Spring AOP 通知的执行顺序

1. 结论

官方文档:Spring AOP Advice

故各种通知的执行顺序:

  • Spring版本5.2.7以后:
    • @Around环绕通知前置操作
    • @Before前置通知
    • 目标方法
    • @AfterReturnin返回通知或@AfterThrowing异常通知
    • @After后置通知
    • @Around环绕通知后置操作
  • Spring版本5.2.7一千:
    • @Around环绕通知前置操作
    • @Before前置通知
    • 目标方法
    • @Around环绕通知后置操作
    • @After后置通知
    • @AfterReturnin返回通知或@AfterThrowing异常通知

2. 示例

java 复制代码
/**
 * 接口类
 */
public interface CalculatorService {
    
    int add(int i, int j);
    
    int sub(int i, int j);
    
    int mul(int i, int j);
    
    int div(int i, int j);
    
}
java 复制代码
import com.example.aop.service.CalculatorService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 接口实现类
 */
@Slf4j
@Component
public class CalculatorServiceImpl implements CalculatorService {

    @Override
    public int add(int i, int j) {
        int result = i + j;
        log.info("方法内部 result = {}", result);
        // 为了测试效果,模拟异常信息
        // int a = 1/0;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        log.info("方法内部 result ={} ", result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        log.info("方法内部 result ={} ", result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        log.info("方法内部 result = {}", result);
        return result;
    }


}

切面类

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 自定义日志切面类
 *
 * @author : Sakura
 * @className : LogAspect
 * @description : 日志切面类
 */
@Component  // 交由IoC容器进行管理
@Aspect     // 声明为切面类
@Slf4j
public class LogAspect {

    // 设置切入点和通知类型

    /**
     * 前置通知:在被代理的目标方法前执行
     * 设置前置通知  @Before(value = "切入点表达式配置切入点")
     * 切入点表达式:
     * 下面切入点设置为:public int CalculatorImpl 所有方法 任意参数类型
     */
    @Before(value = "execution(public int com.example.aop.service.impl.CalculatorServiceImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint) {
        // 使用JoinPoint获取方法签名、方法参数等相关信息
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        log.info("Logger--@Before前置通知,方法名称:{},方法参数:{}", methodName, Arrays.toString(args));
    }


    /**
     * 返回通知:在被代理的目标方法成功结束后执行
     * 设置返回通知:  @AfterReturning(value = "切入点表达式配置切入点", returning = "res")
     * res: 目标方法的返回结果
     */
    @AfterReturning(value = "execution(public int com.example.aop.service.impl.CalculatorServiceImpl.*(..))", returning = "result")
    public void AfterReturningMethod(JoinPoint joinPoint, Object result) {
        // 使用JoinPoint获取方法签名、方法参数等相关信息
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        log.info("Logger--@AfterReturning返回通知,方法名称:{},方法参数:{},返回结果:{}", methodName, Arrays.toString(args), result);
    }


    /**
     * 后置通知:在被代理的目标方法最终结束后执行
     * 设置前置通知:  @After(value = "切入点表达式配置切入点")
     */
    @After(value = "execution(public int com.example.aop.service.impl.CalculatorServiceImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        // 使用JoinPoint获取方法签名、方法参数等相关信息
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        log.info("Logger--@After后置通知,方法名称:{},方法参数:{}", methodName, Arrays.toString(args));


    }

    /**
     * 异常通知:在被代理的目标方法异常结束后执行
     * 设置异常通知:  @AfterThrowing(value = "切入点表达式配置切入点", throwing = "ex")
     * ex: 代指异常信息
     */
    @AfterThrowing(value = "execution(public int com.example.aop.service.impl.CalculatorServiceImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
        // 使用JoinPoint获取方法签名、方法参数等相关信息
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        log.info("Logger--@AfterThrowing异常通知,方法名称:" + methodName + ",异常信息:" + ex.getMessage());

    }

    /**
     * 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
     * 设置异常通知:  @Around(value = "切入点表达式配置切入点)
     * 注:可以使用JoinPoint的子类 ProceedingJoinPoint 调用目标方法
     */
    @Around(value = "execution(public int com.example.aop.service.impl.CalculatorServiceImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        // 使用JoinPoint获取方法签名、方法参数等相关信息
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        Object result = null;
        try {
            log.info("Logger--@Around环绕通知------目标方法执行之前");
            // 使用JoinPoint的子类 ProceedingJoinPoint 调用目标方法
            result = joinPoint.proceed();
            log.info("Logger--@Around环绕通知------目标方法执行之后");
        } catch (Throwable e) {
            e.printStackTrace();
            log.info("Logger--@Around环绕通知------目标方法出现异常执行");
        } finally {
            log.info("Logger--@Around环绕通知------目标方法执行完毕后执行");
        }
        return result;
    }

}

测试:

当spring-aop版本为5.2.7.RELEASE:

目标方法正常执行

复制代码
2024-07-08 17:06:54.148  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之前
2024-07-08 17:06:54.148  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@Before前置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:06:54.154  INFO 260808 --- [           main] c.e.a.s.impl.CalculatorServiceImpl       : 方法内部 result = 5
2024-07-08 17:06:54.154  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@AfterReturning返回通知,方法名称:add,方法参数:[2, 3],返回结果:5
2024-07-08 17:06:54.154  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@After后置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:06:54.154  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之后
2024-07-08 17:06:54.154  INFO 260808 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行完毕后执行

目标方法异常执行:

复制代码
2024-07-08 17:06:05.156  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之前
2024-07-08 17:06:05.156  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@Before前置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:06:05.162  INFO 320568 --- [           main] c.e.a.s.impl.CalculatorServiceImpl       : 方法内部 result = 5
2024-07-08 17:06:05.163  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@AfterThrowing异常通知,方法名称:add,异常信息:/ by zero
2024-07-08 17:06:05.163  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@After后置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:06:05.163  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法出现异常执行
2024-07-08 17:06:05.163  INFO 320568 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行完毕后执行
java.lang.ArithmeticException: / by zero
	at com.example.aop.service.impl.CalculatorServiceImpl.add(CalculatorServiceImpl.java:19)
	at com.example.aop.service.impl.CalculatorServiceImpl$$FastClassBySpringCGLIB$$a70e6bfb.invoke(<generated>)

当spring-aop版本为5.2.6.RELEASE:

目标方法正常执行

复制代码
2024-07-08 17:04:28.394  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之前
2024-07-08 17:04:28.394  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@Before前置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:04:28.400  INFO 330008 --- [           main] c.e.a.s.impl.CalculatorServiceImpl       : 方法内部 result = 5
2024-07-08 17:04:28.400  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之后
2024-07-08 17:04:28.400  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行完毕后执行
2024-07-08 17:04:28.400  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@After后置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:04:28.401  INFO 330008 --- [           main] com.example.aop.config.LogAspect         : Logger--@AfterReturning返回通知,方法名称:add,方法参数:[2, 3],返回结果:5

目标方法异常执行,可以看到 5.2.6.RELEASE@Around通知导致异常被捕获了,没有触发 @AfterThrowing 通知,反而触发了 @AfterReturning 通知方法的执行;而5.2.7.RELEASE触发 了@AfterThrowing 通知

复制代码
2024-07-08 17:04:57.640  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行之前
2024-07-08 17:04:57.640  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@Before前置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:04:57.647  INFO 315420 --- [           main] c.e.a.s.impl.CalculatorServiceImpl       : 方法内部 result = 5
2024-07-08 17:04:57.647  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法出现异常执行
2024-07-08 17:04:57.648  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@Around环绕通知------目标方法执行完毕后执行
2024-07-08 17:04:57.648  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@After后置通知,方法名称:add,方法参数:[2, 3]
2024-07-08 17:04:57.648  INFO 315420 --- [           main] com.example.aop.config.LogAspect         : Logger--@AfterReturning返回通知,方法名称:add,方法参数:[2, 3],返回结果:null
java.lang.ArithmeticException: / by zero
	at com.example.aop.service.impl.CalculatorServiceImpl.add(CalculatorServiceImpl.java:19)
	at com.example.aop.service.impl.CalculatorServiceImpl$$FastClassBySpringCGLIB$$a70e6bfb.invoke(<generated>)
相关推荐
霸道流氓气质22 分钟前
智能客服组件:分层意图识别、RAG Advisor 与人工转接状态机的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
captain3761 小时前
MyBatis操作数据库(入门2)
java·数据库·spring boot·后端·spring·mybatis
心中有你02141 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
步行cgn2 小时前
Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示
java·spring·mvc
IT_陈寒2 小时前
Java序列化踩坑记:对象循环引用让我加班两小时
前端·人工智能·后端
lgcgkCQ2 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
泡海椒2 小时前
05-雷达图多维画像-信用评级多维画像
后端
王中阳Go2 小时前
5.27 分钟干完后端和全部测试:一场三小时笔试的五个失分点,四个不在代码上
大数据·数据库·人工智能·后端·大语言模型
大哥43092 小时前
Modbus 的"字序":一个配错就变成天文数字,或者干脆变成 0
后端
Wang's Blog2 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友