Spring AOP 有哪些通知类型?多个切面的执行顺序是怎样的?
简化版
Spring AOP 有五种通知(Advice) 类型:@Before(前置,方法执行前)、@After(后置,方法执行后,无论成功失败都执行)、@AfterReturning(返回后,方法正常返回才执行)、@AfterThrowing(异常后,方法抛异常才执行)、@Around(环绕,最强大,能完全控制方法执行前后)。单个切面内的执行顺序:@Around 前 → @Before → 目标方法 → @Around 后 → @After → @AfterReturning/@AfterThrowing。多个切面时,用 @Order(值越小优先级越高)控制顺序,进入时高优先级先执行、退出时高优先级后执行(洋葱模型)。
详细版
五种通知类型:
| 通知 | 执行时机 | 典型用途 |
|---|---|---|
@Before | 目标方法执行前 | 参数校验、权限检查、记录开始 |
@After | 目标方法执行后(finally,成功/异常都执行) | 资源清理、记录结束 |
@AfterReturning | 目标方法正常返回后 | 处理/记录返回值 |
@AfterThrowing | 目标方法抛异常后 | 异常日志、告警 |
@Around | 环绕(方法前后都能控制,可决定是否执行、改参数改返回值) | 事务、性能监控、缓存 |
单个切面内的执行顺序:
@Aspect @Component
public class LogAspect {
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("1. @Around 前");
Object r = pjp.proceed(); // 执行目标方法
System.out.println("5. @Around 后");
return r;
}
@Before("pointcut()") public void before() { System.out.println("2. @Before"); }
@After("pointcut()") public void after() { System.out.println("6. @After"); }
@AfterReturning("pointcut()") public void afterRet(){ System.out.println("7. @AfterReturning"); }
@AfterThrowing("pointcut()") public void afterThr(){ System.out.println("@AfterThrowing"); }
}
正常执行输出顺序(Spring 5+ / AspectJ 风格):
1. @Around 前
2. @Before
[目标方法执行]
5. @Around 后
6. @After
7. @AfterReturning
(若方法抛异常:@Around 后可能不执行 → @After → @AfterThrowing)
多个切面的顺序:用 @Order(n) 或实现 Ordered 接口,n 越小优先级越高:
两个切面 A(@Order(1)) 和 B(@Order(2)),洋葱模型:
进入:A.@Before → B.@Before → 目标方法
退出:B.@After → A.@After
高优先级(A)最先进、最后出(包在最外层)
⚠️ Spring 4 和 Spring 5 的单切面内通知顺序有差异:Spring 4 里
@After和@AfterReturning的顺序可能和直觉相反;Spring 5+ 修正为符合 AspectJ 标准:@Around 后 → @After → @AfterReturning/@AfterThrowing。面试按 Spring 5+ 的顺序答即可。
完整版教学
一、AOP 的核心概念先对齐
讲通知类型前,先统一 AOP 的术语(常考):
切面(Aspect):横切逻辑的模块,如 LogAspect(一个 @Aspect 类)
连接点(JoinPoint):能被增强的点,Spring AOP 里就是"方法执行"
切点(Pointcut):用表达式定义"增强哪些连接点",如 execution(* com.xx.service.*.*(..))
通知(Advice):在连接点做什么 + 什么时候做,就是 @Before/@After/@Around 这些
织入(Weaving):把通知应用到目标对象、生成代理的过程
一句话串起来:切面(Aspect)= 切点(在哪切)+ 通知(切了做什么)。通知类型解决的是「在方法执行的哪个时机、做什么」——五种通知对应五个不同的时机点。理解这个框架,再看五种通知就清晰了。
二、五种通知:对应方法执行的五个时机
五种通知本质是「在目标方法执行的不同时刻插入逻辑」,用一条时间线看清:
目标方法执行
@Before → ┌──────────┐ → @AfterReturning(正常返回时)
(方法前) │ method │ @AfterThrowing(抛异常时)
└──────────┘ → @After(无论如何都执行,像 finally)
╰──────────── @Around 把整个过程都包起来 ────────────╯
- @Before:方法前,最简单,做校验/准备。不能阻止方法执行(要阻止用 @Around)。
- @After:方法后,无论成功还是异常都执行(像 finally),做清理。
- @AfterReturning:只在正常返回时执行,能拿到返回值。
- @AfterThrowing:只在抛异常时执行,能拿到异常。
- @Around:最强大——它把目标方法调用包在中间(
pjp.proceed()才真正执行方法),可以在方法前后都插逻辑、能决定要不要执行方法、能改参数、能改返回值、能吞异常。事务、缓存、性能监控都用 @Around(因为要完全控制)。
关键区别:@Around 是「环绕」,控制力最强(其他四种只是「在某个时机被动触发」,无法阻止或改变方法执行)。所以能用简单通知就别用 @Around,需要完全掌控(如缓存要「命中就不执行方法直接返回」)才用 @Around。
三、单个切面内的执行顺序
一个切面里同时有多种通知时,执行顺序是固定的(Spring 5+ 遵循 AspectJ 标准):
正常返回时:
@Around 前置部分
@Before
→ 目标方法执行 →
@Around 后置部分
@After
@AfterReturning
抛出异常时:
@Around 前置部分
@Before
→ 目标方法抛异常 →
(@Around 的 proceed() 后面代码不执行,除非 catch)
@After
@AfterThrowing
记忆要点:@Around 包在最外(前置最先、后置在 @Before 之后目标方法后),@Before 紧贴方法前,@After 像 finally 一定执行,@AfterReturning/@AfterThrowing 二选一。注意 @Around 的后置部分在 @After 之前——因为 @Around 是「包裹式」的,它的后半段紧跟 proceed() 之后,而 @After 是独立的 finally 语义,逻辑上更靠后。
四、@Around 的独特能力:完全掌控
@Around 值得单独讲,因为它和其他四种有本质区别——它能控制目标方法要不要执行、怎么执行:
@Around("pointcut()")
public Object cacheAround(ProceedingJoinPoint pjp) throws Throwable {
String key = buildKey(pjp.getArgs());
Object cached = cache.get(key);
if (cached != null) {
return cached; // ★ 缓存命中,直接返回,根本不执行目标方法!
}
Object result = pjp.proceed(); // 缓存未命中,才执行目标方法
cache.put(key, result); // 把结果放缓存
return result; // 可以改返回值
}
看到了吗?@Around 能做其他通知做不到的事:① 决定是否执行方法(缓存命中就不执行);② 改参数(proceed(newArgs));③ 改返回值(return 别的);④ 捕获并处理异常(try-catch 包住 proceed)。这就是为什么事务(要在异常时回滚、正常时提交)、缓存(命中就短路)、重试(失败就再 proceed)都用 @Around。代价是要手动调 pjp.proceed(),忘了调目标方法就不执行了。
五、多个切面的执行顺序:@Order 与洋葱模型
当一个方法被多个切面同时增强(如同时有日志切面和事务切面),顺序由 @Order 决定,遵循洋葱模型:
@Aspect @Order(1) @Component class AspectA { } // 优先级高
@Aspect @Order(2) @Component class AspectB { } // 优先级低
@Order 值越小,优先级越高,包在越外层:
进入(前置类通知):A.@Before → B.@Before → 目标方法
退出(后置类通知):B.@After → A.@After
像洋葱:优先级高的 A 在最外层(最先进、最后出)
优先级低的 B 在内层(后进、先出)
规律:高优先级切面「先进后出」(包在外层),低优先级「后进先出」(在内层)。这个顺序很重要——比如「事务切面」和「日志切面」,你可能希望日志在最外层(记录包括事务在内的完整耗时),就把日志的 @Order 设得更小(优先级更高)。Spring 内部的事务切面有默认的 Order,自定义切面要注意和它的相对顺序。
六、实战:通知类型选型与顺序设计
按需求选通知类型:
| 需求 | 用哪个通知 |
|---|---|
| 只在方法前做事(校验、日志开始) | @Before |
| 只在方法后清理(无论成败) | @After |
| 处理正常返回值 | @AfterReturning |
| 处理异常(告警、日志) | @AfterThrowing |
| 需要控制执行/改参数返回值/事务/缓存/重试 | @Around |
原则:能用简单通知(@Before/@After)就别用 @Around——@Around 要手动 proceed、容易出错(忘了 proceed 方法就不执行、异常处理不当会吞异常)。只有确实需要「完全控制方法执行」时才用 @Around。多切面时,用 @Order 明确设置优先级,别依赖默认顺序(多个切面默认顺序不确定,容易出诡异问题)。
记忆钩子:「五通知:@Before(前)、@After(后,像finally)、@AfterReturning(正常返回)、@AfterThrowing(异常)、@Around(环绕,最强能控制执行);单切面顺序 @Around前→@Before→方法→@Around后→@After→@AfterReturning/Throwing;多切面 @Order 越小越外层,先进后出」。
七、常见误区与追问
- 误区:@After 只在方法成功时执行。 @After 无论成功还是抛异常都执行(像 finally);只在成功时执行的是 @AfterReturning,只在异常时的是 @AfterThrowing。
- 误区:@Before 能阻止目标方法执行。 不能,@Before 只是在方法前触发,无法阻止;要阻止/控制执行只能用 @Around(不调 proceed 就不执行方法)。
- 误区:@Around 里忘了 proceed 也没关系。 忘了调
pjp.proceed(),目标方法根本不会执行,返回值也不对——这是 @Around 最常见的 bug。 - 误区:多个切面的顺序是固定的。 不设 @Order 时多切面顺序不确定;要用 @Order(越小优先级越高、越在外层)明确控制。
- 追问:@Around 相比其他四种通知的优势是什么? 完全控制目标方法——能决定是否执行、能改参数和返回值、能捕获处理异常,所以事务、缓存、重试都用它;其他四种只能被动在某时机触发。
- 追问:多个切面时,@Order(1) 和 @Order(2) 谁先执行? @Order(1) 优先级高,包在外层:前置通知 @Order(1) 先执行,后置通知 @Order(1) 后执行(先进后出,洋葱模型)。
- 追问:Spring 5 和 Spring 4 的通知顺序有区别吗? 有,Spring 5 修正为符合 AspectJ 标准(@Around 后→@After→@AfterReturning);Spring 4 里 @After 和 @AfterReturning 顺序可能反直觉,面试按 Spring 5+ 答。
八、加强记忆
Spring AOP 的五种通知对应目标方法执行的五个时机:@Before(方法前,做校验但不能阻止执行)、@After(方法后,像 finally 无论成败都执行,做清理)、@AfterReturning(正常返回后,能拿返回值)、@AfterThrowing(抛异常后,能拿异常)、@Around(环绕,最强——把方法包在 pjp.proceed() 中间,能决定是否执行、改参数、改返回值、处理异常,事务/缓存/重试都用它,但忘了 proceed 方法就不执行)。单个切面内顺序(Spring 5+):@Around 前 → @Before → 目标方法 → @Around 后 → @After → @AfterReturning/@AfterThrowing。多个切面用 @Order 控制(值越小优先级越高、包在越外层),遵循洋葱模型——高优先级切面「先进后出」、低优先级「后进先出」。选型原则:能用简单通知就别用 @Around,多切面务必用 @Order 明确顺序。一句话「五通知对应五时机、@Around 最强能控制执行、单切面 Around 包最外、多切面 @Order 越小越外层先进后出」。