什么是AOP

面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术

AOP的基本概念

  • Aspect(切面):通常是一个类,里面可以定义切入点和通知

  • JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

  • Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

  • Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

  • AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

AOP通知类型介绍

  • Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

  • AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

  • AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象

  • After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式

  • Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

AOP的顺序是: 首先进入Around --> 执行joinPoint.proceed()之前,触发Before --> 然后执行joinPoint.proceed() --> 执行joinPoint.proceed()之后,触发After --> 最后触发AfterReturning

备注:多个AOP执行顺序

当创建了多个自定义注解,并标记到同一个方法上,可以通过设置Order来指定执行顺序。

方式一:添加注解@Order

@Aspect
@Component
@Order(1)
public class LogFilter1Aspect {}

方式二:实现Ordered接口

@Aspect
@Component
public class LogFilter2Aspect implements Ordered {
    @Override
    public int getOrder() {
        return 1;
    }
}

多AOP执行顺序: Order越小的越优先执行,但是,After方法反而越后面执行

AOP 应用场景

  • Logging 日志

  • Security 权限

  • Transaction management事务管理

  • Auditing, 审核

  • Caching 缓存

  • Internationalization 国际化

  • Error detection and correction 错误处理

  • Memory management 内存管理

  • Performance monitoring 性能监控

  • Synchronization 同步

Spring AOP基于注解的“零配置”方式实现

package com.spring.aop;
​
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
​
/*
 * 声明一个切面
 * 自动注解AOP
 */
@Aspect
@Component
public class LogAspect {
    // 配置切点 及要传的参数   
    @Pointcut("@annotation(com.sytms.common.annotation.Log)")
    public void pointCut(){
​
    }
​
    // 配置连接点 方法开始执行前通知
    @Before("pointCut()")
    public void beforeLog() {
        System.out.println("开始执行前置通知  日志记录");
    }
    // 方法执行完后通知
    @After("pointCut()")
    public void afterLog() {
        System.out.println("方法执行完后置通知 日志记录");
    }
    // 执行成功后通知
    @AfterReturning("pointCut()")
    public void afterReturningLog() {
        System.out.println("方法成功执行后通知 日志记录");
    }
    // 抛出异常后通知
    @AfterThrowing("pointCut()")
    public void afterThrowingLog() {
        System.out.println("方法抛出异常后通知 日志记录");
    }
​
    // 环绕通知
    @Around("pointCut()")
    public Object aroundLog(ProceedingJoinPoint joinpoint) {
        Object result = null;
        try {
            System.out.println("环绕通知开始 日志记录");
            long start = System.currentTimeMillis();
​
            //有返回参数 则需返回值
            result = joinpoint.proceed();
​
            long end = System.currentTimeMillis();
            System.out.println("总共执行时长" + (end - start) + " 毫秒");
            System.out.println("环绕通知结束 日志记录");
        } catch (Throwable t) {
            System.out.println("出现错误");
        }
        return result;
    }    
}