基于本文回答
0
评论

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. 正常返回(无异常)的情况:

  1. @Around(环绕通知:joinPoint.proceed() 之前的代码)
  2. @Before(前置通知)
  3. [ 目标方法执行 ]
  4. @AfterReturning(返回通知)
  5. @After(后置/最终通知,相当于 finally
  6. @Around(环绕通知:joinPoint.proceed() 之后的代码)

⚠️ 与 Spring 4 的重要区别
在 Spring 4 中,正常执行时 @After 会在 @AfterReturning 之前执行。
在 Spring 5 中,纠正了这一顺序:先触发返回值 @AfterReturning,再执行类似 finally 的 @After

2. 抛出异常的情况:

  1. @Around(环绕通知:joinPoint.proceed() 之前的代码)
  2. @Before(前置通知)
  3. [ 目标方法执行抛出异常 ]
  4. @AfterThrowing(异常通知)
  5. @After(后置/最终通知,相当于 finally
  6. 注:@AroundjoinPoint.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) (低优先级)

正常情况下的执行顺序将是:

  1. Aspect_A@Around (前) / @Before
  2. Aspect_B@Around (前) / @Before
  3. [ 目标方法执行 ]
  4. Aspect_B@AfterReturning / @After / @Around (后)
  5. 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)。这能体现出你对框架底层变动有敏锐的掌握度。

右滑查看面试常问