官方文档
推荐看链接文档原文,简单翻译一下:
“当多条通知都希望在同一连接点上运行时会发生什么? Spring AOP遵循与AspectJ相同的优先级规则来确定通知执行的顺序。 优先级最高的通知首先“on the way in(进入时)”运行(因此,如果给定两个before 通知,优先级最高的通知首先运行)。从连接点“On the way out(离开时)”,最高优先级的通知最后运行(因此,给定两个after通知,具有最高优先级的通知将第二个运行)。
org.springframework.core.Ordered 接口或使用 @Order 注解对其进行注解。 给定两个切面,从 Ordered.getValue()(或 @Order 注解值)返回较低值的切面具有较高的优先级。
从概念上讲,特定切面的每个不同通知类型都意味着直接应用于连接点。因此,@AfterThrowing 通知方法不应该从附带的@After/@AfterReturning 类型通知方法接收异常。
springframework5.2.7开始,在同一 @Aspect 类中定义的需要在同一连接点上运行的通知方法根据其通知类型按以下顺序分配优先级:从最高到最低优先级:@Around、@Before、@After、@AfterReturning、@AfterThrowing。但是,请注意,@After 通知方法将在同一切面的任何 @AfterReturning 或 @AfterThrowing 通知方法之后有效地调用,遵循 AspectJ 对 @After 的“After finally advice”语义。
当在同一 @Aspect 类中定义的同一类型的两条通知(例如,两个@After 通知方法)都需要在同一连接点上运行时,顺序是不明确的(因为没有办法通过反射来获取javac编译类的源代码声明顺序)。考虑在每个 @Aspect 类中的每个连接点将这样的两个通知方法折叠成一个通知方法,或者将通知方法片段重构成单独的 @Aspect 类,您可以通过 Ordered 接口或 @Order 在切面级别进行排序。”
测试
测试所用 Spring 版本:5.2.7.RELEASE
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
同一切面不同通知类型执行顺序
测试代码:
@Service
public class TestService {
public String test(){
System.out.println("test() execute ...");
//int i = 1 / 0;
return "hello world";
}
}
定义切面和通知:
@Aspect
@Component
public class AopConfig {
@Before("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void before(){
System.out.println("AopConfig Before advice exec ...");
}
@Around("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AopConfig Around advice start exec ...");
pjp.proceed();
System.out.println("AopConfig Around advice end exec ...");
}
@After("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void after(){
System.out.println("AopConfig After advice exec ...");
}
@AfterThrowing("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void afterThrowing(){
System.out.println("AopConfig AfterThrowing advice exec ...");
}
@AfterReturning("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void afterReturning(){
System.out.println("AopConfig AfterReturning advice exec ...");
}
}
正常情况输出:
AopConfig Around advice start exec ...
AopConfig Before advice exec ...
test() execute ...
AopConfig AfterReturning advice exec ...
AopConfig After advice exec ...
AopConfig Around advice end exec ...
异常情况,TestService.test 方法去掉注释,int i = 1 /0 除0异常;另外 Around 通知捕获一下异常:
@Around("execution (* com.dxc.opentalk.springtest.service.TestService.*())")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AopConfig Around advice start exec ...");
try{
pjp.proceed();
} catch (Exception e){
System.out.println(e);
}
System.out.println("AopConfig Around advice end exec ...");
}
异常情况下输出:
AopConfig Around advice start exec ...
AopConfig Before advice exec ...
test() execute ...
AopConfig AfterThrowing advice exec ...
AopConfig After advice exec ...
java.lang.ArithmeticException: / by zero
AopConfig Around advice end exec ...
结论:Spring 5.2.7.RELEASE 版本之后,正如开头译文所述:在同一 @Aspect 类中定义的需要在同一连接点上运行的通知方法根据其通知类型按以下顺序分配优先级:从最高到最低优先级:@Around、@Before、@After、@AfterReturning、@AfterThrowing。但是,请注意,@After 通知方法将在同一切面的任何 @AfterReturning 或 @AfterThrowing 通知方法之后有效地调用,遵循 AspectJ 对 @After 的“After finally advice”语义。 其中,@Around 通知包裹了其余四种通知方法。
注意点:
如果我们使用 Spring 5.2.7 之前的版本,比如 Spring 5.0.5 版本,上面异常情况输出结果:
AopConfig Around advice start exec ...
AopConfig Before advice exec ...
test() execute ...
java.lang.ArithmeticException: / by zero
AopConfig Around advice end exec ...
AopConfig After advice exec ...
AopConfig AfterReturning advice exec ...
可以看出,Around 通知并没有包裹其余类型通知,而且 After 通知在 AfterReturning 通知之前执行了。还有就是,5.2.7 版本中 Around 通知虽然捕获异常,但是因为是包裹其他通知,所以 除0异常仍会触发 AfterThrowing 通知;但是 5.0.5 版本就因为 After 通知退出先执行,导致异常被捕获了,没有触发 AfterThrowing 通知,反而触发了 AfterReturning 通知方法的执行。所以,在实际使用中如果需要考虑不同类型通知的执行顺序,一定要弄清楚采用Spring的版本。