Skip to content

AOP 的应用

说明

  1. SpringBoot 中,org.springframework.boot.autoconfigure.aop.AopAutoConfiguration 默认开启 AOP,故无需显示添加 @EnableAspectJAutoProxy 注解。
  2. 如需手动关闭,在 application.yaml 中添加 spring.aop.auto=false 即可。
  3. 通知注解说明:
    • @Before 是在所拦截方法执行之前执行一段逻辑。
    • @After 是在所拦截方法执行之后执行一段逻辑。
    • @Around 是可以同时在所拦截方法的前后执行一段逻辑(写在 point.proceed() 方法前就是之前执行, 写在 point.proceed() 方法后就是之后执行)。
    • @AfterReturning finally 块中执行
    • @AfterThrowing 捕获到异常会执行

执行顺序:Before, After, AfterReturning, AfterThrowing

  1. 如果需要在多个通知方法之间传递参数,可以使用 ThreadLocal。详情参考 com.github.mengweijin.quickboot.framework.log.RequestLogAop.java 或者直接使用 @Around 来处理所有的业务逻辑,就不用使用 @Before, @After 等注解了。

使用

  1. 定义一个类,添加 @Aspect 和 @Component 注解;
  2. 定义一个无参方法,添加 @Pointcut 注解作为切点,并配置切点表达式(可以配置扫描包、类、注解等)。
  3. 定义通知方法,根据需要添加通知注解 @Before、@After 等。注解中配置切点方法,并在方法内实现业务逻辑。

Advisor 接口

读过源码的朋友可能会想到使用 org.springframework.aop.Advisor 接口来代替使用 @Aspect 来实现 AOP。

但是从 Advisor 接口中的类注释来看,官方明确说明了这个接口不是给 spring 用户来使用的,所以还是推荐使用注解的方式。

使用示例

java
@Aspect
@Component
public class AopDemo {

    @Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
    public void logPointCut() {
    }

    @Before("logPointCut()")
    public void before(JoinPoint joinPoint){
        // do something
    }

    /**
     * @param joinPoint 切点
     * @param object 返回的对象,参数名必须和注解中配置的名称保持一致。
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "object")
    public void afterReturning(JoinPoint joinPoint, Object object) {
        // do something
    }

    /**
     * 拦截异常操作
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        // do something
    }
}

在 AOP 类中获取方法参数

  1. 使用 @Before, @After 等注解时,都可以在注解的方法参数中添加 JoinPoint 或 ProceedingJoinPoint(@Around 注解使用) 参数,通过这两个类,都可以获取参数。
  2. 在添加了 @Aspect 注解的类中的方法中,可以使用如下代码获取参数:
java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Object[] arguments = methodInvocation.getArguments();

JoinPoint 和 ProceedingJoinPoint 中获取参数的原理也都是通过上面的 MethodInvocation 获取的。

在 AOP 类中获取方法上面的注解

需要通过 JoinPoint 来获取方法上面的注解。

java
 public static <T extends Annotation> T getMethodAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
     Signature signature = joinPoint.getSignature();
     MethodSignature methodSignature = (MethodSignature) signature;
     Method method = methodSignature.getMethod();
     return method == null ? null : method.getAnnotation(annotationClass);
}

你也许会想到使用如下的代码来获取类上面的注解,但这有可能是行不通的。你可能拿不到对应的注解。 因为此时的方法对象并不是原始的方法,而是通过代理生成的代理对象的方法。而代理方法上肯定是没有你添加的注解的。

java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Method method = methodInvocation.getMethod();
Annotation annotation = method.getAnnotation(annotationClass);

于是,你会想到,那能不能直接拿到原始方法,从而获取的原始方法上的注解呢?答案是可以的。

java
MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();
Method method = methodInvocation.getMethod();
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
boolean hasAnnotation = AnnotatedElementUtils.hasAnnotation(specificMethod, annotationClass);

从方法和构造函数中获取参数名称

DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); String[] parameterNames = discoverer.getParameterNames(constructor);