目录
- 1. 问题描述
- 2. 结论
- 结论1:"对于同一个切入点,同一个切面不同类型的通知的执行顺序"
- 结论2:"对于同一个切入点,不同切面不同类型通知的执行顺序"
- 3. 测试
- 环境:SpringBoot 2.3.4.RELEASE
- 测试集合1:针对结论1,单个切面类的情况。
- 测试1:切入点正常执行完,无异常。
- 测试2:切入点抛出异常
- 测试3:@AfterReturning执行了注解属性returning,表示需要返回值
- 测试集合2:针对结论2,多个切面类的情况
- 4. 参考
- 5. 结语:如果对大家有帮助,请点赞支持。如果有问题随时在评论中指出,感谢。
1. 问题描述
在Spring AOP中,对于同一个切入点,可能会有多个切面多种不同类型的通知共同作用于它,那么这些来自不同切面的不同类型通知,它们的执行顺序是怎样的?本文将答案分成2部分讲述。
- 对于同一个切入点,同一个切面不同类型的通知的执行顺序。
- 对于同一个切入点,不同切面不同类型通知的执行顺序。
文章后续安排:section 2直接给出结论,图文结合,更好理解。section 3讲述测试过程。section 4讲述参考来源。大家可以根据自己需要查看相应部分,想看结论可以直接看section2。
2. 结论
结论1:“对于同一个切入点,同一个切面不同类型的通知的执行顺序”
- 图片描述
- 文字描述
@Around(before)
@Before
#切入点方法#
@AfterReturing/@AfterThrowing
(1. 假如有异常,执行@AfterThrowing。2. 假如没异常,
2.1 假如@AfterReturning中没有设置returning属性,@AfterReturning修饰的方法会被执行。
2.2 假如@AfterReturning中设置了returning属性,当切入点方法拥有返回值时,@AfterReturning修饰的方法会被执行,否则不执行)
@After (不管有没有异常,都执行)
@Around(after)
(1. 切入点方法抛出异常而且没有捕获,不执行。2. 切入点方法没有异常,或者异常被捕获,执行。)
结论2:“对于同一个切入点,不同切面不同类型通知的执行顺序”
- 图片描述
- 文字描述
切入点方法之前,优先级越高的切面的通知,越先被执行。
切入点方法之后,优先级越高的切面的通知,越后被执行。
切面优先级可以通过在切面类上的"定义注解@Order"或者”实现Ordered接口中的getOrder()决定”,值越小,优先级越高。
3. 测试
环境:SpringBoot 2.3.4.RELEASE
测试集合1:针对结论1,单个切面类的情况。
测试1:切入点正常执行完,无异常。
- 切面类:各种通知都有
@Component
@Aspect
public class CommonAspect1 {
@Pointcut("execution(* cn.edu.szu.flow.control.service.UserService.*(..))")
private void pointCut() {}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around(before)");
Object val = joinPoint.proceed();
System.out.println("Around(after)");
return val;
}
@Before("pointCut()")
public void before() {
System.out.println("Before");
}
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("AfterReturning");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("AfterThrowing");
}
@After("pointCut()")
public void after() {
System.out.println("After");
}
}
- 切入点方法
@Service
public class UserService {
public void addUser() {
System.out.println("切入点方法执行......");
}
- 调用切入点方法
@SpringBootApplication
public class FlowControlApplication {
public static void main(String[] args) {
SpringApplication.run(FlowControlApplication.class, args);
}
@Autowired
private UserService userService;
@PostConstruct
public void postConstruct() {
// --------------调用切入点方法--------------
userService.addUser();
}
}
- 结果:符合结论1
测试2:切入点抛出异常
- 切面类:和测试1相同
- 切入点方法:在测试1基础上修改,让它抛出异常。
@Service
public class UserService {
public void addUser() {
System.out.println("切入点方法执行......");
System.out.println("切入点方法抛出异常......");
int i = 1/0;
}
- 调用切入点方法:和测试1相同
- 结果:符合结论1。AfterThrowing执行,AfterReturning不执行,Around没有捕获异常,因此后面的不执行。
测试3:@AfterReturning执行了注解属性returning,表示需要返回值
- 切面类:在测试1基础上,只修改@AfterReturning
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturning(String result) {
System.out.println("AfterReturning");
}
- 切入点方法:不抛出异常
@Service
public class UserService {
public void addUser() {
System.out.println("切入点方法执行......");
}
- 调用切入点方法:和测试1相同
- 结果:符合结论1。@AfterReturning不执行,因为切入点方法是void,而@AfterReturning指明了需要返回值。
测试集合2:针对结论2,多个切面类的情况
- 切面1:包含不同类型的通知,并使用@Order指明优先级。值越小,优先级越高。
@Order(0)
@Component
@Aspect
public class CommonAspect1 {
@Pointcut("execution(* cn.edu.szu.flow.control.service.UserService.*(..))")
private void pointCut() {}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around(before).One");
Object val = joinPoint.proceed();
System.out.println("Around(after).One");
return val;
}
@Before("pointCut()")
public void before() {
System.out.println("Before.One");
}
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("AfterReturning.One");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("AfterThrowing.One");
}
@After("pointCut()")
public void after() {
System.out.println("After.One");
}
}
- 切面2:和切面1类似,包含不同类型的通知,并使用@Order指明优先级。值越小,优先级越高。
@Order(1)
@Component
@Aspect
public class CommonAspect2 {
@Pointcut("execution(* cn.edu.szu.flow.control.service.UserService.*(..))")
private void pointCut() {}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around(before).Two");
Object val = joinPoint.proceed();
System.out.println("Around(after).Two");
return val;
}
@Before("pointCut()")
public void before() {
System.out.println("Before.Two");
}
@AfterReturning(value = "pointCut()")
public void afterReturning() {
System.out.println("AfterReturning.Two");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("AfterThrowing.Two");
}
@After("pointCut()")
public void after() {
System.out.println("After.Two");
}
}
- 切入点方法
@Service
public class UserService {
public void addUser() {
System.out.println("切入点方法执行......");
}
- 执行切入点方法
@SpringBootApplication
public class FlowControlApplication {
public static void main(String[] args) {
SpringApplication.run(FlowControlApplication.class, args);
}
@Autowired
private UserService userService;
@PostConstruct
public void postConstruct() {
// --------------调用切入点方法--------------
userService.addUser();
}
}
- 符合结论2:在切入点方法之前,优先级高的先执行。在切入点方法之后,优先级高的后执行。
- 调换2个切面的优先级:让切面2的优先级更高
- 结果:符合结论2。在切入点方法之前,优先级高的先执行。在切入点方法之后,优先级高的后执行。
4. 参考
- Spring 5.3.39 docs
本文测试使用SpringBoot2.3.4.RELEASE,内部使用的是Spring5.x,所以这里看的也是5.x的文档(Spring6.x文档关于这部分的内容和Spring5.x是一样的)。
- 参考1说:在切入点方法之前,优先级越高的通知越先执行。在切入点访问之后,优先级越高的通知越后执行。
- 参考2说:在不同的切面中,可以通过给切面类”添加@Order”或者”实现Ordered接口”来指定切面的优先级,从而决定不同切面中通知的优先级。如果不指定,则执行顺序不可知。
- 参考3说:在同一个切面中,不同类型的通知优先级由高到低分别是@Around, @Before, @After, @AfterReturning, @AfterThrowing。注意,结合参考1,@After和@AfterReturning, @AfterThrowing都是在切入点之后执行的,优先级越高的通知越后执行,因此执行顺序是@AfterThrowing,@AfterReturning,@After。
- 结合参考1+参考3+上述测试 得出结论1。
- 结合参考1+参考2+上述测试 得出结论2。
- 同一切面内通知的执行顺序:细节不多,但启发了我直接去看官方文档。