Spring 5 之后,多个 AOP 通知(如 @Before、@After、@Around 等)的执行顺序。
在 Spring 5.0(对应 Spring Boot 2.0)及之后的版本中,Spring 对 AOP 的底层实现进行了重构,使其完全遵循了 AspectJ 的标准语义。
这一改动解决之前版本(Spring 4 及更早)中通知执行顺序反直觉的问题。在 Spring 5 之后,AOP 通知的执行顺序呈现出完美的“同心圆(洋葱)模型”。
以下是具体的执行顺序详解:
一、 同一个切面(Aspect)内的通知执行顺序
假设我们在同一个切面类中定义了 @Around、@Before、@After、@AfterReturning 和 @AfterThrowing。
1. 正常返回(无异常)的情况:
@Around(环绕通知:joinPoint.proceed()之前的代码)@Before(前置通知)- [ 目标方法执行 ]
@AfterReturning(返回通知)@After(后置/最终通知,相当于finally)@Around(环绕通知:joinPoint.proceed()之后的代码)
⚠️ 与 Spring 4 的重要区别:
在 Spring 4 中,正常执行时@After会在@AfterReturning之前执行。
在 Spring 5 中,纠正了这一顺序:先触发返回值@AfterReturning,再执行类似 finally 的@After。
2. 抛出异常的情况:
@Around(环绕通知:joinPoint.proceed()之前的代码)@Before(前置通知)- [ 目标方法执行抛出异常 ]
@AfterThrowing(异常通知)@After(后置/最终通知,相当于finally)- 注:
@Around中joinPoint.proceed()之后的代码不会执行,除非你在@Around内部用try-catch捕获了异常并改变了正常流程。
二、 伪代码模型(帮助记忆)
你可以把 Spring 5 之后的 AOP 逻辑想象成下面这种嵌套的 try-catch-finally 结构:
java
// @Around 环绕通知开始
try {
// @Before 前置通知
try {
// 【执行目标方法】
// @AfterReturning 返回通知
} catch (Exception e) {
// @AfterThrowing 异常通知
throw e;
} finally {
// @After 最终通知
}
// @Around 环绕通知结束
} catch (Exception e) {
// 环绕通知处理异常
}
三、 多个不同切面(Aspect)之间的执行顺序
如果有多个切面类同时拦截同一个方法,它们的执行顺序由 @Order 注解 或实现 Ordered 接口 来决定。
- 规则:
@Order(值)的值越小,优先级越高。 - 洋葱模型特性:
- 入栈(方法执行前):优先级高的切面先执行。
- 出栈(方法执行后):优先级高的切面后执行。
假设有两个切面:
Aspect_A配置了@Order(1)(高优先级)Aspect_B配置了@Order(2)(低优先级)
正常情况下的执行顺序将是:
Aspect_A的@Around(前) /@BeforeAspect_B的@Around(前) /@Before- [ 目标方法执行 ]
Aspect_B的@AfterReturning/@After/@Around(后)Aspect_A的@AfterReturning/@After/@Around(后)
四、 验证代码示例
如果你在 Spring Boot 2.x/3.x 跑一段测试代码,控制台打印出的顺序严格如下:
java
@Aspect
@Component
public class MyAspect {
@Around("execution(* com.example.service.MyService.doSomething(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("1. @Around 前置处理");
Object result = pjp.proceed();
System.out.println("6. @Around 后置处理");
return result;
}
@Before("execution(* com.example.service.MyService.doSomething(..))")
public void before() {
System.out.println("2. @Before 执行");
}
@AfterReturning("execution(* com.example.service.MyService.doSomething(..))")
public void afterReturning() {
System.out.println("4. @AfterReturning 执行");
}
@After("execution(* com.example.service.MyService.doSomething(..))")
public void after() {
System.out.println("5. @After 执行");
}
}
控制台输出(目标方法打印 "3. 目标方法执行"):
plaintext
1. @Around 前置处理
2. @Before 执行
3. 目标方法执行
4. @AfterReturning 执行
5. @After 执行
6. @Around 后置处理
总结
面试时如果问到这个问题,一定要先点明 Spring 5 的改动(遵循了 AspectJ 标准),再说出具体的顺序(先返回/异常,再 After)。这能体现出你对框架底层变动有敏锐的掌握度。
右滑查看面试常问