Spring AOP的通知(Advice)是切面编程的核心,但你是否被各种通知注解的执行顺序绕晕过?比如,@Around和@Before谁先执行?方法抛异常时@After和@AfterThrowing如何触发?本文从源码层面解析通知的执行顺序,帮你彻底理清逻辑!
一、Spring AOP的5种通知注解
| 通知类型 | 注解 | 作用 |
|---|---|---|
| 前置通知 | @Before | 在目标方法执行前触发(例如权限校验)。 |
| 后置通知 | @After | 在目标方法执行后触发(无论是否抛异常,类似finally)。 |
| 返回通知 | @AfterReturning | 在目标方法正常返回后触发(例如记录返回值)。 |
| 异常通知 | @AfterThrowing | 在目标方法抛出异常后触发(例如记录异常日志)。 |
| 环绕通知 | @Around | 包裹目标方法,可控制方法执行(例如计算耗时、事务管理)。 |
二、通知的执行顺序
假设一个方法被多个通知注解修饰,执行顺序如下:
-
@Around最先触发,最后结束:它包裹整个方法执行。
-
@Before在目标方法前执行:在@Around的
proceed()之前。 -
@After在目标方法后执行:无论是否抛异常,类似
finally。 -
@AfterReturning和@AfterThrowing互斥:根据方法是否抛异常触发其中一个。
三、从源码看通知执行顺序
Spring AOP的通知链由拦截器链(Interceptor Chain) 实现,核心逻辑在ReflectiveMethodInvocation类中。以下是关键源码片段:
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
private int currentInterceptorIndex = -1;
public Object proceed() throws Throwable {
// 1. 按顺序执行所有拦截器(即通知)
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 执行目标方法
return invokeJoinpoint();
}
Object interceptor = interceptorsAndDynamicMethodMatchers.get(++currentInterceptorIndex);
if (interceptor instanceof MethodInterceptor) {
MethodInterceptor mi = (MethodInterceptor) interceptor;
// 2. 执行当前拦截器(通知)
return mi.invoke(this);
}
// ...
}
}
源码解析:
-
拦截器链顺序:通知按
@Around→@Before→@After→@AfterReturning/@AfterThrowing的顺序加入链中。 -
执行流程:
-
@Around拦截器最先执行,调用proceed()后触发后续拦截器。 -
@Before在@Around内部proceed()之前执行。 -
@After在@Around的proceed()之后执行(无论是否抛异常)。
-
四、代码示例验证执行顺序
1. 定义切面
@Aspect
@Component
public class OrderAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("@Around开始");
Object result = pjp.proceed();
System.out.println("@Around结束");
return result;
}
@Before("servicePointcut()")
public void before() {
System.out.println("@Before");
}
@After("servicePointcut()")
public void after() {
System.out.println("@After");
}
@AfterReturning("servicePointcut()")
public void afterReturning() {
System.out.println("@AfterReturning");
}
@AfterThrowing("servicePointcut()")
public void afterThrowing() {
System.out.println("@AfterThrowing");
}
}
2. 执行结果(正常返回)
@Around开始
@Before
目标方法执行...
@After
@AfterReturning
@Around结束
3. 执行结果(抛异常)
@Around开始
@Before
目标方法抛异常...
@After
@AfterThrowing
@Around结束
五、为什么@Around要包裹其他通知?
源码答案:
-
@Around是最灵活的通知类型,需要手动调用proceed()执行目标方法。 -
在
proceed()前后可以插入任意逻辑,因此它必须作为拦截器链的起点和终点。
设计逻辑:
Spring将@Around视为最外层的“包装器”,其他通知(如@Before、@After)实际上是通过MethodInterceptor实现的,而@Around的拦截器优先级更高。
六、常见问题与避坑指南
问题1:多个切面的通知顺序如何控制?
答案:
-
使用
@Order注解指定切面优先级,值越小优先级越高。 -
高优先级切面的
@Around会更早执行,但内部的@Before会在低优先级切面的@Around之后执行。
问题2:@AfterThrowing未触发?
可能原因:
-
异常被
@Around或@After中的try-catch吞掉了。 -
确保在通知中抛出异常,或不在
@Around中捕获异常。
问题3:自调用导致AOP失效?
原因:类内部方法调用不会经过代理对象。
解决方案:通过AopContext.currentProxy()获取代理对象调用。
七、总结
-
通知类型:5种注解覆盖方法执行全生命周期。
-
执行顺序:
@Around包裹所有通知,@After类似finally始终执行。 -
源码逻辑:通过拦截器链按顺序触发通知。
1394

被折叠的 条评论
为什么被折叠?



