Spring AOP 切面的先后顺序详解

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() 逐层深入再逐层返回。理解这个栈式结构,就能正确安排事务、日志、权限、缓存的相对位置。

相关推荐
ym hyd 1111 小时前
宝宝早教服务系统源码 Java+SpringBoot+Vue3 前后分离
java·开发语言·spring boot
自强的小白1 小时前
微服务之间的依赖传递和调用(实体类的模块传递以及负载均衡的调用!)注册中心
java·微服务
她说彩礼65万2 小时前
C语言 单引号与双引号
java·服务器·c语言
知守观2 小时前
百万级数据导出OOM:POI的坑与EasyExcel的流式写入实战(附内存对比)
java·后端
anew___2 小时前
《从零手写操作系统 (19):Ext2文件系统实战——从内存到磁盘的跨越》
java·服务器·前端·数据库·算法
小尹哥-程序员2 小时前
第6集:RAG检索不准?3个参数调优路线图
人工智能·spring
蜗牛互联网2 小时前
Computer Use公共预览的安全设计:应用、动作与数据三维门禁
java·人工智能·后端·安全·策略模式
FYKJ_20102 小时前
SSM校园互助与闲置交易平台62145-计算机课程设计、毕业设计
java·spring boot·python·mysql·架构·spark·课程设计
wangbing11252 小时前
下划线数字字面量
java