Spring AOP #
Spring AOP启用对AspectJ的支持 #
- 添加aspectjweaver.jar
- 通过@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() {
// ...
}
}