aop

Spring AOP #

Spring AOP启用对AspectJ的支持 #

  1. 添加aspectjweaver.jar
  2. 通过@EnableAspectJAutoProxy注解或者<aop:aspectj-autoproxy/>开启支持

在SpringBoot框架中无需手动配置,详见 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

Ponitcut(切入点) #

Spring AOP 官方文档 #

pointcut designators: https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-pointcuts-designators

pointcut examples: https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#aop-pointcuts-examples

Designator(指示符) #

支持以下指示符(可以通过&&||!符号来进行逻辑组合):

  • execution: 匹配方法执行连接点
  • within: 匹配连接点所在类或包
  • this: 匹配实现了指定接口的代理实例
  • target: 匹配实现了指定接口的被代理对象
  • args: 匹配给定参数类型的连接点
  • @target: 匹配实现了指定接口的被代理对象的注解
  • @within: 匹配连接点所在类的注解
  • @annonation: 匹配连接点的注解
  • bean: 匹配bean名称

指示符示例:

text
// 匹配任意公共方法
execution(public * *(..))

// 匹配名称以set开头的、参数只有一个的方法
execution(* set*(*))

// 匹配AccountService接口(类)的方法
execution(* com.xyz.service.AccountService.*(..))

// 匹配service包中的方法
execution(* com.xyz.service.*.*(..))

// 匹配service包及其子包中的方法
execution(* com.xyz.service..*.*(..))

// 匹配service包中实现的方法
within(com.xyz.service.*)

// 匹配DemoService类中实现的方法(不包括子类)
within(com.xyz.service.DemoService)

// 匹配DemoService类及其子类中实现的方法
within(com.xyz.service.DemoService+)

// 匹配实现了AccountService接口(类)的代理实例
this(com.xyz.service.AccountService)

// 匹配实现了AccountService接口(类)的被代理对象
target(com.xyz.service.AccountService)

// 匹配名称为 myExecutor 的 bean
bean(myExecutor)

// 匹配名称后缀为Executor的bean
bean(*Executor)

this/target/within/execution 四者区别:

text
// interface<Demo> : abstract void save()
// class<DemoImpl> : public void save() {}
// 无论Demo类是接口、抽象类还是实现类,save方法是抽象方法还是实现方法,都不影响以下结论。

// bean<Demo> : new DemoImpl()
// invoke : (bean<Demo>).save()

this(Demo)  // 匹配
this(DemoImpl)  // 取决于代理方式是CGLIB代理还是JDK代理,默认前者。前者能匹配,因为代理对象的类型是DemoImpl的子类;后者不能匹配,因为代理对象是一个实现了Demo接口的代理实例,不是DemoImpl实例。

target(Demo)  // 匹配,因为目标对象的类型是DemoImpl,属于Demo子类。
target(DemoImpl)  // 匹配,因为目标对象的类型是DemoImpl。

within(Demo)  // 不匹配,因为目标对象的类型等于DemoImpl,不等于Demo。
within(Demo+)  // 匹配
within(DemoImpl)  // 匹配

execution(* Demo.*(..))  // 匹配。这个用例与 target(Demo) 等效。
execution(* DemoImpl.*(..))  // 匹配。这个用例与 target(DemoImpl) 等效。
  • this:匹配代理对象本身的类型,判断它是否是指定的类或其子类。
  • target:匹配目标对象的类型,判断它是否是指定的类或其子类。
  • within:匹配目标对象的类型,判断它是否等于指定的类。

引用已定义的切入点 #

可以通过方法名进行引用:

java
class Demo {
    @Pointcut("execution(public * *(..))")
    public void anyPublicOperation() {}

    @Pointcut("within(com.xyz.myapp.trading..*)")
    private void inTrading() {}

    // 引用当前类中的切入点
    @Pointcut("anyPublicOperation() && inTrading()")
    private void tradingOperation() {}
}

class Demo2 {
    // 引用其他类中的切入点(该切入点方法必须可以被访问)
    @Pointcut("com.example.Demo.anyPublicOperation()")
    private void anyPublicOperation2() {}
}

带参数的切入点 #

args/this/target/@within/@target/@args 这些指示符都可以用以下方式来定义切入点:

java
class DemoAspect {
    // 定义带参数的切入点。
    // args(foo, ..)可以用来限制参数匹配范围,同时可以将目标方法的参数值传递给增强方法。
    @Pointcut("args(foo, ..)")
    public void withArgs(Foo foo) {}

    // 引用带参数的切入点
    @Pointcut("withArgs(foo)")
    public void withArgs2(Foo foo) {}
}

interface Demo<T> {
    void save(T param);
    void saveAll(Collection<T> param);
}

class DemoGenericAspect {
    // 可以匹配泛型类型参数
    @Pointcut("execution(* Demo+.save(*)) && args(param)")
    public void savePointcut(MyType param) {}

    // 无法匹配指定泛型类型的集合。
    // 建议将 Collection<MyType> 改为 Collection<?> ,然后手动匹配集合内的元素。
    @Pointcut("execution(* Demo+.saveAll(*)) && args(param)")
    public void saveAllPointcut(Collection<MyType> param) {}
}

Advice(增强) #

增强顺序:

  • @Before
  • @Around: operations before pjp.proceed()
  • @Around: pjp.proceed()
  • @Around: operations after pjp.proceed()
  • @AfterReturning or @AfterThrowing
  • @After
  • @Finally

多切面顺序:根据切面bean顺序,在进入时按优先级从高到低执行,在退出时按优先级从低到高执行。

示例代码:

java
@Component
@AspectJ
public class DemoAspect {
    // 定义切入点
    @Pointcut("execution(* com..Demo.save(..))")
    private void myPointcut() {
    }

    // 引用已定义的切入点
    @Before("myPointcut()")
    private void withMyPointcut() {
        // ...
    }

    // 编写切入点表达式,而不是引用已定义的切入点
    @Before("execution(* com..Demo.save(..))")
    private void withoutMyPointcut() {
        // ...
    }

    // JoinPoint类型参数必须放在第一个参数位置上,或者不带任何参数。
    @Before("myPointcut()")
    private void beforeExecution(JoinPoint joinPoint) {
        Object that = joinPoint.getThis();
        Object target = joinPoint.getTarget();
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();

        // "save"
        String signatureName = signature.getName();
        // com.example.Test$DemoImpl
        Class<?> signatureDeclaringType = signature.getDeclaringType();
        // "boolean com.example.Test$DemoImpl.save(String,int)"
        String signatureString = signature.toString();
        // public boolean com.example.Test$DemoImpl.save(java.lang.String,int)
        Method signatureMethod = ((MethodSignature) signature).getMethod();
        // "boolean"
        Class<?> signatureReturnType = ((MethodSignature) signature).getReturnType();

        // "method-execution"
        String kind = joinPoint.getKind();
        // "execution(public boolean com.example.Test$DemoImpl.save(String,int))"
        String string = joinPoint.toString();
    }

    // 环绕增强的返回值必须为Object类型,第一个参数必须为ProceedingJoinPoint类型。
    @Around("myPointcut()")
    public Object aroundExecution(ProceedingJoinPoint pjp) throws Throwable {
        try {
            System.out.println("Before");
            // 不调用或多次调用 pjp.proceed 也是合法的。
            return pjp.proceed();

            // 带参 proceed 方法用于替换参数,对于SpringAOP实现,只需传递目标方法所需所有参数即可,
            // 与AspectJ编译器定义的行为不一样,不要被 org.aspectj.lang.ProceedingJoinPoint 中的注释误导。
            //return pjp.proceed(new Object[]{"ABC"});
        } finally {
            System.out.println("After");
        }
    }

    @AfterReturning(pointcut = "myPointcut()", returning = "ret")
    public void afterReturning(Object ret) {
        // ...
    }

    @AfterThrowing(pointcut = "myPointcut()", throwing = "ex")
    public void afterThrowing(Exception ex) throws Throwable {
        // ...
    }

    // 最后执行的方法(finally)
    @After("myPointcut()")
    public void afterExecution() {
        // ...
    }
}
2024年7月8日