步行cgn头像
关注

Spring AOP 切面的先后顺序详解

Spring AOP 切面的先后顺序详解

当多个切面作用于同一个方法时,谁先执行、谁后执行,直接影响到事务、日志、权限等逻辑的正确性。理解切面顺序的控制方式和执行规律,是写好 AOP 的关键。

一、默认顺序:不可预测

如果不指定顺序,多个切面的执行顺序由 Spring 容器加载 Bean 的顺序决定,而 Bean 的加载顺序又受包扫描顺序、类名、依赖关系等因素影响。结果是不确定的,不同环境、不同启动顺序下可能不一样。

@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 注解

数值越小,优先级越高,越先执行。

@Aspect
@Component
@Order(1)
public class LogAspect {
    // 优先级高,先执行
}

@Aspect
@Component
@Order(2)
public class TransactionAspect {
    // 优先级低,后执行
}

2. 实现 Ordered 接口

@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. 完整示例

@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:

@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. 事务切面与日志切面

通常事务切面的优先级要高于日志切面,让事务在外层:

@Aspect
@Component
@Order(1)  // 事务在外层
public class TransactionAspect { }

@Aspect
@Component
@Order(2)  // 日志在内层
public class LogAspect { }

这样日志记录的是事务提交后的行为,如果事务回滚,日志也能正确记录异常。

2. 权限切面与业务切面

权限校验应该在最外层,先校验再执行:

@Aspect
@Component
@Order(1)  // 权限校验优先
public class PermissionAspect { }

@Aspect
@Component
@Order(2)
public class TransactionAspect { }

权限不通过时直接抛异常,不需要开启事务。

3. 缓存切面与事务切面

缓存切面通常在外层,先查缓存,未命中再进入事务:

@Aspect
@Component
@Order(1)  // 缓存在外层
public class CacheAspect { }

@Aspect
@Component
@Order(2)  // 事务在内层
public class TransactionAspect { }

六、Spring 内置切面的顺序

Spring 内置了一些切面,它们的 @Order 值:

切面作用默认 Order
ExposeInvocationInterceptor暴露 AopContextOrdered.HIGHEST_PRECEDENCE + 1
AspectJPointcutAdvisorAspectJ 切面由 @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() 逐层深入再逐层返回。理解这个栈式结构,就能正确安排事务、日志、权限、缓存的相对位置。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_51288065/article/details/166949165

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--