面试官:Spring AOP的通知注解有哪些?它们的执行顺序是什么?

Spring AOP的通知(Advice)是切面编程的核心,但你是否被各种通知注解的执行顺序绕晕过?比如,@Around和@Before谁先执行?方法抛异常时@After和@AfterThrowing如何触发?本文从源码层面解析通知的执行顺序,帮你彻底理清逻辑!


一、Spring AOP的5种通知注解

通知类型注解作用
前置通知@Before在目标方法执行前触发(例如权限校验)。
后置通知@After在目标方法执行后触发(无论是否抛异常,类似finally)。
返回通知@AfterReturning在目标方法正常返回后触发(例如记录返回值)。
异常通知@AfterThrowing在目标方法抛出异常后触发(例如记录异常日志)。
环绕通知@Around包裹目标方法,可控制方法执行(例如计算耗时、事务管理)。

二、通知的执行顺序

假设一个方法被多个通知注解修饰,执行顺序如下:

  1. @Around最先触发,最后结束:它包裹整个方法执行。

  2. @Before在目标方法前执行:在@Around的proceed()之前。

  3. @After在目标方法后执行:无论是否抛异常,类似finally

  4. @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);
        }
        // ... 
    }
}

源码解析

  1. 拦截器链顺序:通知按@Around → @Before → @After → @AfterReturning/@AfterThrowing的顺序加入链中。

  2. 执行流程

    • @Around拦截器最先执行,调用proceed()后触发后续拦截器。

    • @Before@Around内部proceed()之前执行。

    • @After@Aroundproceed()之后执行(无论是否抛异常)。


四、代码示例验证执行顺序

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始终执行。

  • 源码逻辑:通过拦截器链按顺序触发通知。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孙悟饭Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值