Spring AOP 切面的先后顺序详解
当多个切面作用于同一个方法时,谁先执行、谁后执行,直接影响到事务、日志、权限等逻辑的正确性。理解切面顺序的控制方式和执行规律,是写好 AOP 的关键。
一、默认顺序:不可预测
如果不指定顺序,多个切面的执行顺序由 Spring 容器加载 Bean 的顺序决定,而 Bean 的加载顺序又受包扫描顺序、类名、依赖关系等因素影响。结果是不确定的,不同环境、不同启动顺序下可能不一样。
java
@Aspect
@Component
public class LogAspect {
@Before("serviceLayer()")
public void before() { System.out.println("LogAspect"); }
}
@Aspect
@Component
public class TransactionAspect {
@Before("serviceLayer()")
public void before() { System.out.println("TransactionAspect"); }
}
两个切面谁先执行,无法保证。生产环境中依赖默认顺序是危险的,必须显式指定。
二、控制顺序的两种方式
1. @Order 注解
数值越小,优先级越高,越先执行。
java
@Aspect
@Component
@Order(1)
public class LogAspect {
// 优先级高,先执行
}
@Aspect
@Component
@Order(2)
public class TransactionAspect {
// 优先级低,后执行
}
2. 实现 Ordered 接口
java
@Aspect
@Component
public class LogAspect implements Ordered {
@Override
public int getOrder() {
return 1;
}
}
两种方式等价。@Order 更简洁,Ordered 接口更灵活(可以在运行时动态计算顺序)。
三、执行顺序的规律
多个切面作用于同一个方法时,执行顺序遵循栈结构:优先级高的切面在外层,优先级低的在内层。
假设有两个切面,LogAspect 的 @Order(1),TransactionAspect 的 @Order(2)。
1. @Before 的执行顺序
数值小的先执行。
LogAspect @Before ← @Order(1) 先执行
TransactionAspect @Before ← @Order(2) 后执行
目标方法
2. @After 的执行顺序
数值大的先执行。
目标方法
TransactionAspect @After ← @Order(2) 先执行
LogAspect @After ← @Order(1) 后执行
3. 整体结构
LogAspect @Before
TransactionAspect @Before
目标方法
TransactionAspect @After
LogAspect @After
@Before 按 @Order 升序执行,@After 按 @Order 降序执行。就像穿衣服和脱衣服:先穿内衣再穿外套,脱的时候先脱外套再脱内衣。
4. 完整示例
java
@Aspect
@Component
@Order(1)
public class LogAspect {
@Before("serviceLayer()")
public void before() {
System.out.println("1. LogAspect @Before");
}
@After("serviceLayer()")
public void after() {
System.out.println("6. LogAspect @After");
}
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("2. LogAspect @Around 前");
Object result = pjp.proceed();
System.out.println("5. LogAspect @Around 后");
return result;
}
}
@Aspect
@Component
@Order(2)
public class TransactionAspect {
@Before("serviceLayer()")
public void before() {
System.out.println("3. TransactionAspect @Before");
}
@After("serviceLayer()")
public void after() {
System.out.println("4. TransactionAspect @After");
}
}
输出:
1. LogAspect @Before
2. LogAspect @Around 前
3. TransactionAspect @Before
(目标方法执行)
4. TransactionAspect @After
5. LogAspect @Around 后
6. LogAspect @After
执行顺序分析:
@Around在@Before之后,但在其他切面的@Before之前。- 目标方法执行完后,先执行内层切面的
@After,再执行外层切面的@Around后置逻辑和@After。
5. @Around 的嵌套关系
@Around 的 proceed() 调用链体现了切面的嵌套结构:
LogAspect @Around 前
TransactionAspect @Around 前
目标方法
TransactionAspect @Around 后
LogAspect @Around 后
@Order 小的切面在外层,proceed() 时进入内层,执行完内层后返回外层。
四、多个切面的完整执行流程
有三个切面,@Order 分别为 1、2、3:
java
@Aspect @Component @Order(1)
public class AspectA {
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("A 前");
Object result = pjp.proceed();
System.out.println("A 后");
return result;
}
}
@Aspect @Component @Order(2)
public class AspectB {
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("B 前");
Object result = pjp.proceed();
System.out.println("B 后");
return result;
}
}
@Aspect @Component @Order(3)
public class AspectC {
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("C 前");
Object result = pjp.proceed();
System.out.println("C 后");
return result;
}
}
输出:
A 前
B 前
C 前
(目标方法执行)
C 后
B 后
A 后
结构就像一个洋葱,A 在最外层,C 在最内层。proceed() 逐层深入,返回时逐层退出。
五、特殊场景的顺序
1. 事务切面与日志切面
通常事务切面的优先级要高于日志切面,让事务在外层:
java
@Aspect
@Component
@Order(1) // 事务在外层
public class TransactionAspect { }
@Aspect
@Component
@Order(2) // 日志在内层
public class LogAspect { }
这样日志记录的是事务提交后的行为,如果事务回滚,日志也能正确记录异常。
2. 权限切面与业务切面
权限校验应该在最外层,先校验再执行:
java
@Aspect
@Component
@Order(1) // 权限校验优先
public class PermissionAspect { }
@Aspect
@Component
@Order(2)
public class TransactionAspect { }
权限不通过时直接抛异常,不需要开启事务。
3. 缓存切面与事务切面
缓存切面通常在外层,先查缓存,未命中再进入事务:
java
@Aspect
@Component
@Order(1) // 缓存在外层
public class CacheAspect { }
@Aspect
@Component
@Order(2) // 事务在内层
public class TransactionAspect { }
六、Spring 内置切面的顺序
Spring 内置了一些切面,它们的 @Order 值:
| 切面 | 作用 | 默认 Order |
|---|---|---|
ExposeInvocationInterceptor |
暴露 AopContext |
Ordered.HIGHEST_PRECEDENCE + 1 |
AspectJPointcutAdvisor |
AspectJ 切面 | 由 @Order 决定 |
TransactionInterceptor |
事务 | Ordered.LOWEST_PRECEDENCE(默认) |
AsyncAnnotationBeanPostProcessor |
异步 | 默认较低 |
TransactionInterceptor 默认的 Order 是 Ordered.LOWEST_PRECEDENCE,也就是最内层。如果自定义切面不加 @Order,它的默认值也是 LOWEST_PRECEDENCE,可能和事务切面顺序不确定。
七、常见问题
1. 不加 @Order 会怎样
多个切面都不加 @Order 时,顺序取决于 Bean 加载顺序,不可控 。生产环境必须显式指定 @Order。
2. @Order 值相同会怎样
@Order 值相同时,顺序仍然不确定。应保证每个切面的 @Order 值唯一。
3. @Order 范围
@Order 接受 int 类型。常用范围是 0 到 100,或者使用 Ordered.HIGHEST_PRECEDENCE(Integer.MIN_VALUE)和 Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)。
4. 方法级 @Order
@Order 也可以标注在方法上,控制同一个切面内多个通知的顺序。但同一个切面内的通知顺序比较固定(@Around 外层,@Before 内层),一般不需要方法级 @Order。
5. 切面内部顺序
同一个切面内,@Around 在 @Before 之前,@After 在 @Around 之后。这个顺序由 Spring 内部决定,不受 @Order 影响。
八、总结
| 维度 | 规律 |
|---|---|
| 控制方式 | @Order 注解或 Ordered 接口 |
| 数值规律 | 数值越小,优先级越高 |
@Before 顺序 |
优先级高的先执行(升序) |
@After 顺序 |
优先级高的后执行(降序) |
@Around 结构 |
优先级高的在外层,proceed() 进入内层 |
| 默认顺序 | 不可预测,必须显式指定 |
| 典型排列 | 权限(1) → 日志(2) → 事务(3) → 缓存(4) 或按业务需求调整 |
切面顺序的核心规律是:优先级高的在外层,优先级低的在内层 。@Before 从外到内,@After 从内到外,@Around 通过 proceed() 逐层深入再逐层返回。理解这个栈式结构,就能正确安排事务、日志、权限、缓存的相对位置。