Spring AOP 学习笔记 之 Advice详解

学习材料:https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/advice.html

1. 什么是 Advice(通知)

定义:Advice 是 AOP 的核心概念之一,表示在特定的连接点(Join Point)上执行的代码逻辑。
作用:通过 Advice,可以在方法调用前后、异常抛出时等位置插入自定义逻辑。

2. Advice 的类型

Spring AOP 提供了以下几种类型的 Advice:

2.1 @Before Advice

@Before 注解用于声明前置通知(Before Advice),即在目标方法执行之前执行自定义逻辑。
使用场景:适用于需要在方法执行前进行某些操作的场景,例如日志记录、权限检查等。
参数:@Before 注解可以接受一个切入点表达式,用于指定哪些方法执行前需要应用该Advice。
方法签名:@Before 方法可以有参数,但这些参数必须是Spring AOP支持的参数类型,例如 JoinPoint、ProceedingJoinPoint(仅用于 @Around)、JoinPoint.StaticPart 等。

Pointcut表达式包含在注解里的liline版示例:

@Aspect
public class BeforeExample {@Before("execution(* com.xyz.dao.*.*(..))")public void doAccessCheck() {// ...}
}

 

Before注解里包含的是Pointcut的signature,真正的Pointcut表达式在@Pointcut注解里定义。

@Aspect
public class BeforeExample {@Before("com.xyz.CommonPointcuts.dataAccessOperation()")public void doAccessCheck() {// ...}
}@Aspect
public CommonPointcuts {@Pointcut("execute * com.xyz.dao.*.*(..)")public void dataAccessOperation() {}}

 

2.2 @AfterReturing, @AfterThrowing, @After Advice

在Spring AOP中,@AfterReturning、@AfterThrowing 和 @After 是三种不同的通知(Advice)类型,用于在连接点(Join Point)的不同阶段执行自定义逻辑。以下是它们的详细说明:


@AfterReturning
作用:在目标方法成功执行并返回结果后执行。
使用场景:适用于需要在方法成功执行后进行日志记录、资源清理等操作的场景。
参数:可以接收返回值作为参数,通过returning属性指定参数名。

    @Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@AfterReturning(pointcut = "log()", returning = "result")public void afterReturning(Object result) {log.info("LogAspect afterReturning ..., result: {}", result);}

@AfterThrowing
作用:在目标方法抛出异常后执行。
使用场景:适用于需要在方法抛出异常时进行异常处理、日志记录等操作的场景。
参数:可以接收异常对象作为参数,通过throwing属性指定参数名。

    @Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@AfterThrowing(pointcut = "log()", throwing = "exception")public void afterThrowing(Exception exception) {log.info("LogAspect afterThrowing ...", exception);}

@After
作用:无论目标方法是否成功执行,都会在方法执行后执行。
使用场景:适用于需要在方法执行后进行资源清理、日志记录等操作的场景,不关心方法的执行结果。
参数:不接收任何特定参数。

@Slf4j
@Aspect
@Component
public class LogAspect {@Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@After("log()")public void after() {log.info("LogAspect after ...");}
}

 

2.3 @Around Advice

Around Advice 是一种特殊的Advice,它会在匹配的方法执行前后运行。它有机会在方法执行前后执行自定义逻辑,并且可以决定方法是否执行、何时执行以及如何执行。
使用场景:Around Advice通常用于需要在方法执行前后共享状态的场景,例如启动和停止计时器。
最佳实践:总是使用满足需求的最弱形式的Advice。如果Before Advice已经足够满足需求,则不要使用Around Advice。


返回类型:Around Advice方法的返回类型应为Object。
参数:方法的第一个参数必须是ProceedingJoinPoint类型。
执行方法:在Around Advice方法体内,必须调用proceed()方法来执行目标方法。调用proceed()方法时,如果不带参数,会将调用者原始的参数传递给目标方法。

@Slf4j
@Aspect
@Component
public class LogAspect {@Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@Around("log()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("LogAspect around start ...");long start = System.currentMillis();Object result = joinPoint.proceed();log.info("LogAspect around end ...");long end = System.currentMillis();System.out.println("cost time: " + (end-start) + "ms");return result;}
}

3 Advice的执行顺序

3.0 测试环境

使用的springboot测试依赖如下:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>3.3.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.3.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>3.3.6</version></dependency>

下面测试的Aspect准备拦截Controller的方法,Controller代码如下: 

@Slf4j
@RestController
public class HelloCtroller {@GetMapping("/hello")public String hello() {log.info("execute method: hello()");return "hello";}@GetMapping("/divide")public Integer divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b) {log.info("execute method: divide(), a: {}, b: {}", a, b);return a/b;}
}

 

3.1 单个Aspect的各Advice执行顺序

可以看到我们LogAspect的整体代码如下:

@Slf4j
@Aspect
@Component
public class LogAspect {@Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@Around("log()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("LogAspect around start ...");Object result = joinPoint.proceed();log.info("LogAspect around end ...");return result;}@Before("log()")public void before() {log.info("LogAspect before ...");}@AfterReturning(pointcut = "log()", returning = "result")public void afterReturning(Object result) {log.info("LogAspect afterReturning ..., result: {}", result);}@AfterThrowing(pointcut = "log()", throwing = "exception")public void afterThrowing(Exception exception) {log.info("LogAspect afterThrowing ...", exception);}@After("log()")public void after() {log.info("LogAspect after ...");}
}

 

3.1.1 方法正常返回的执行顺序

HelloController.hello() 方法会正常执行,并返回String结果。

我们执行 /hello地址的请求:

http://localhost:8080/hello

后台打印的切面日志如下:

2025-04-15T13:58:26.858+08:00  INFO 25536 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2025-04-15T13:58:26.888+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect around start ...
2025-04-15T13:58:26.889+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect before ...
2025-04-15T13:58:26.889+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.ctroller.HelloCtroller         : execute method: hello()
2025-04-15T13:58:26.889+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect afterReturning ..., result: hello
2025-04-15T13:58:26.889+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect after ...
2025-04-15T13:58:26.889+08:00  INFO 25536 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect around end ...

调用方法的正常返回顺序如下:

@Around start --> @Before advice --> orginal method  --> @AfterReturing --> @After --> @Around end.

3.1.2 方法碰到异常的执行顺序

HelloController.divide(Integer a, Integer b)方法, 当b=0的时候,就会抛出除零异常。

http://localhost:8080/divide?a=2&b=0

执行的Aspect拦截日志如下:

2025-04-15T14:05:03.612+08:00  INFO 25536 --- [nio-8080-exec-5] org.derek.aspect.LogAspect               : LogAspect around start ...
2025-04-15T14:05:03.613+08:00  INFO 25536 --- [nio-8080-exec-5] org.derek.aspect.LogAspect               : LogAspect before ...
2025-04-15T14:05:03.613+08:00  INFO 25536 --- [nio-8080-exec-5] org.derek.ctroller.HelloCtroller         : execute method: divide(), a: 2, b: 0
2025-04-15T14:05:03.613+08:00  INFO 25536 --- [nio-8080-exec-5] org.derek.aspect.LogAspect               : LogAspect afterThrowing ...

java.lang.ArithmeticException: / by zero
    at org.derek.ctroller.HelloCtroller.divide(HelloCtroller.java:25) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355) ~[spring-aop-6.1.15.jar:6.1.15]

2025-04-15T14:05:03.620+08:00  INFO 25536 --- [nio-8080-exec-5] org.derek.aspect.LogAspect               : LogAspect after ...
2025-04-15T14:05:03.622+08:00 ERROR 25536 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero
 

可以看到异常情况下,拦截执行顺序如下:

@Around start --> @Before Advice --> orginal method --> @AfterThrowing --> @After (--> @Around end  因为抛出异常这里不再执行)

3.2 多个Aspect的各Advice的执行顺序

1. 默认执行顺序
在 Spring AOP 中,默认情况下,Aspect 的执行顺序是未定义的。如果多个 Aspect 匹配同一个连接点(Join Point),它们的执行顺序可能会根据依赖注入的顺序、类加载顺序或其他因素动态决定。

2. 通过 @Order 注解控制顺序
可以使用 @Order 注解来显式指定 Aspect 的优先级。
数字越小,优先级越高,越先执行。
示例代码:

import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;@Aspect
@Order(1) // 优先级最高
public class FirstAspect {// 定义切入点和通知逻辑
}@Aspect
@Order(2) // 次优先级
public class SecondAspect {// 定义切入点和通知逻辑
}

3. 通过实现 Ordered 接口
如果不想使用 @Order 注解,可以实现 org.springframework.core.Ordered 接口,并重写 getOrder() 方法。
示例代码:

import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;@Aspect
public class FirstAspect implements Ordered {@Overridepublic int getOrder() {return 1; // 优先级最高}
}@Aspect
public class SecondAspect implements Ordered {@Overridepublic int getOrder() {return 2; // 次优先级}
}

这里为了测试实际的Aspect执行顺序,我们使用注解@Order的方式定义了两个切面:

 ControllerAspect.java, 顺序为1.

@Slf4j
@Aspect
@Component
@Order(1) // 优先级,越小越先执行
public class CtrollerAspect {@Pointcut("execution(* org.derek.ctroller.*.*(..))")public void controller() {System.out.println("log");}@Around("controller()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("around advice start ...");Object proceed = joinPoint.proceed();log.info("around advice end ...");return proceed;}@Before("controller()")public void before() {log.info("before advice ...");}@AfterReturning(pointcut = "controller()", returning = "result")public void afterReturning(Object result) {log.info("afterReturning advice ..., result: {}", result);}@AfterThrowing(pointcut = "controller()", throwing = "exception")public void afterThrowing(Exception exception) {log.info("afterThrowing advice ...", exception);}@After("controller()")public void after() {log.info("after advice ...");}
}

LogAspect.java, 顺序为2.

@Slf4j
@Aspect
@Component
@Order(2)
public class LogAspect {@Pointcut("execution(* org.derek.ctroller.*.*(..))")public void log() {}@Around("log()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("LogAspect around start ...");Object result = joinPoint.proceed();log.info("LogAspect around end ...");return result;}@Before("log()")public void before() {log.info("LogAspect before ...");}@AfterReturning(pointcut = "log()", returning = "result")public void afterReturning(Object result) {log.info("LogAspect afterReturning ..., result: {}", result);}@AfterThrowing(pointcut = "log()", throwing = "exception")public void afterThrowing(Exception exception) {log.info("LogAspect afterThrowing ...", exception);}@After("log()")public void after() {log.info("LogAspect after ...");}
}

执行相同的hello方法,调用日志如下:

2025-04-15T13:52:45.672+08:00  INFO 32288 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.CtrollerAspect          : around advice start ...
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.CtrollerAspect          : before advice ...
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect around start ...
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect before ...
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.ctroller.HelloCtroller         : execute method: hello()
2025-04-15T13:52:45.705+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect afterReturning ..., result: hello
2025-04-15T13:52:45.707+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect after ...
2025-04-15T13:52:45.707+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.LogAspect               : LogAspect around end ...
2025-04-15T13:52:45.707+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.CtrollerAspect          : afterReturning advice ..., result: hello
2025-04-15T13:52:45.707+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.CtrollerAspect          : after advice ...
2025-04-15T13:52:45.707+08:00  INFO 32288 --- [nio-8080-exec-1] org.derek.aspect.CtrollerAspect          : around advice end ...

可以看到,多个切面执行顺序如下:

1) @Order(1)的切面方法  @Around start --> @Before Advice

2) @Order(2)的切面方法 @Around start --> @Before Advice 

3) 执行原始的方法 original method

4) @Order(2)的切面方法 @AfterReturning/@AfterThrowing --> @After --> @Adround end

5)@Order(1)的切面方法 @AfterReturning/@AfterThrowing --> @After --> @Adround end

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/76973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数智读书笔记系列029 《代数大脑:揭秘智能背后的逻辑》

《代数大脑:揭秘智能背后的逻辑》书籍简介 作者简介 加里F. 马库斯(Gary F. Marcus)是纽约大学心理学荣休教授、人工智能企业家,曾创立Geometric Intelligence(后被Uber收购)和Robust.AI公司。他在神经科学、语言学和人工智能领域发表了大量论文,并著有《重启AI》等多部…

如何看电脑的具体配置?

李升伟 整理 要查看电脑的具体配置&#xff0c;可以通过系统工具、命令行工具或第三方软件实现&#xff0c;以下是具体方法&#xff1a; 一、系统自带工具查看&#xff08;无需安装软件&#xff09; Windows系统&#xff1a; 系统设置&#xff1a; 右键点击桌面“此电脑”…

开源TTS项目GPT-SoVITS,支持跨语言合成、支持多语言~

简介 GPT-SoVITS 是一个开源的文本转语音&#xff08;TTS&#xff09;项目&#xff0c;旨在通过少量语音数据实现高质量的语音合成。其核心理念是将基于变换器的模型&#xff08;如 GPT&#xff09;与语音合成技术&#xff08;如 SoVITS&#xff0c;可能指“唱歌语音合成”&am…

D1084低功耗LDO稳压器:技术解析与应用设计

引言 在现代电子设计中&#xff0c;低功耗和高效率是至关重要的。D1084是一款5A低功耗低压差线性稳压器&#xff08;LDO&#xff09;&#xff0c;以其出色的负载调节能力和快速瞬态响应&#xff0c;成为低电压微处理器应用的理想选择。本文将深入解析D1084的技术特性和应用设计…

Log4j详解:Java日志系统全指南

文章目录 1. 日志系统简介1.1 什么是日志1.2 为什么使用日志框架1.3 Java中的常见日志框架 2. Log4j概述2.1 Log4j简介2.2 Log4j的版本历史2.3 Log4j与Log4j 2的主要区别 3. Log4j架构与核心组件3.1 Logger&#xff08;日志记录器&#xff09;3.2 日志级别&#xff08;Level&am…

【信息系统项目管理师】高分论文:论信息系统项目的整合管理(银行数据仓库项目)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 正文一、制定项目章程二、制定项目管理计划三、指导和管理项目的实施四、管理项目知识五、监控项目工作六、实施整体变更控制七、结束项目或阶段正文 2023年6月,我以项目经理的身份,参加了 xx银行xx省分行数…

sql server 预估索引大小

使用deepseek工具预估如下&#xff1a; 问题&#xff1a; 如果建立一个数据类型是datetime的索引&#xff0c;需要多大的空间&#xff1f; 回答&#xff1a; 如果建立一个数据类型是 datetime 的索引&#xff0c;索引的大小取决于以下因素&#xff1a; 索引键的大小&#…

干货 | 高性能 Nginx 优化配置总结

文章目录 一、前言二、配置优化2.1 并发处理架构优化2.1.1 工作进程配置2.1.2 事件驱动模型 2.2 传输效率优化2.2.1 零拷贝技术2.2.2 长连接复用 2.3 缓存体系构建2.3.1 文件描述符缓存2.3.2 代理缓存2.3.3 静态资源缓存 2.4 协议层深度优化2.4.1 HTTP/2 支持2.4.2 TLS优化 2.5…

ES DSL 常用修改语句

字段值替换修改 修改sql update zyzkwjj set dhreplace(dh,"WS","WSS") where dh like %WS% update zyzkwjj set dh replace(dh, WS, DZ),ztm replace(ztm, WS, DZ),zrz replace(zrz, WS, DZ) where dh like %WS% or ztm like %WS% or zrz like %WS%…

Vue 3 的组合式 API-hooks

Vue 3 的组合式 API 组合式 API 是 Vue 3 的核心特性之一&#xff0c;它允许开发者将组件的逻辑拆分为可复用的函数。组合式 API 的主要特点是 逻辑复用&#xff1a;将逻辑提取到独立的函数中&#xff0c;方便在多个组件中复用。组织清晰&#xff1a;将相关的逻辑分组&#x…

Web渗透之XSS注入

XSS的类型 1、反射型XSS 我们构建好一个urlXSS的payload&#xff0c;发送给受害者&#xff0c;受害者点击恶意链接后会在受害者的浏览器上执行恶意代码。反射型XSS是一次性的&#xff0c;而且比较容易被发现。通常恶意链接会被修改成短链接&#xff0c;或钓鱼图片的形式。 2…

【Nginx】Nginx代理Tomcat配置及404问题解决

当Tomcat返回HTTP 404未找到错误时&#xff0c;可以通过以下两种方式设置跳转到指定地址&#xff1a; ① 在Tomcat应用内部配置错误页面跳转&#xff08;直接修改Tomcat的Web应用配置&#xff09; ② 在Nginx反向代理层拦截404错误并跳转&#xff08;无需修改Tomcat&#xff0c…

某公司网络OSPF单区域配置

1.配置背景&#xff1a; xx公司网络由三台路由器和一台交换机组成&#xff0c;现在想要三台路由器之间通过OSPF实现互连互通。 2.网络结构如下&#xff1a; 3.具体配置&#xff1a; 3.1路由器 RA 配置&#xff1a; 1.更改主机名称&#xff1a; Router>en Router#conf t…

电脑知识 | TCP通俗易懂详解 <一>

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f68d;什么是TCP/TCP协议 三、&#x1f9cd;‍♂为什么TCP可靠 1.&#x1f970;关于可靠 2.&#x1f920;哪里可靠 3.&#x1f393;️图片的三次握手&#xff0c;四次挥手 4.&#x1f4da;️知识点总结 四、&…

MyBatis 中 Mapper 传递参数的多种方法

# MyBatis Mapper 传递参数的多种方法及其优势 在使用 MyBatis 进行数据库操作时&#xff0c;Mapper 接口的参数传递是一个非常基础但又十分重要的部分。不同的参数传递方式适用于不同的场景&#xff0c;合理选择可以大大提高代码的可读性和维护性。本文将详细介绍几种常见的 …

Dify 插件开发笔记

Dify 插件开发 开发流程 #mermaid-svg-U9rSMmcbWvcGcFMu {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-U9rSMmcbWvcGcFMu .error-icon{fill:#552222;}#mermaid-svg-U9rSMmcbWvcGcFMu .error-text{fill:#552222;st…

行星际激波在日球层中的传播:Propagation of Interplanetary Shocks in the Heliosphere (第二部分)

行星际激波在日球层中的传播&#xff1a;Propagation of Interplanetary Shocks in the Heliosphere &#xff08;第一部分&#xff09;- Chapter 1: Introduction & Chapter 2: Basics of Magnetohydrodynamics 行星际激波在日球层中的传播&#xff1a;Propagation of In…

巴法云平台-TCP设备云-微信小程序实时接收显示数据-原理

微信小程序通过WebSocket或HTTP长轮询连接平台&#xff08;而非直接使用TCP&#xff09;&#xff01;&#xff01;&#xff01; 物联网平台对协议层的一种封装设计——将底层通信协议&#xff08;如TCP&#xff09;与应用层业务逻辑&#xff08;如主题路由&#xff09;解耦&am…

QT Sqlite数据库-教程002 查询数据-上

【1】DQL语句&#xff1a; DQL语句&#xff08;数据查询语言&#xff09;&#xff0c;用来查询数据记录。DQL 基本结构由 SELECT FROM、WHERE、JOIN 等子句构成。DQL 语句并不会改变数据库&#xff0c;而是让数据库将查询结果发送结果集给客户端&#xff0c;返回的结果是一张虚…

基础数学:线性代数与优化理论

本篇文章简单带您复习线性代数与优化理论&#xff08;主要是我发表的文章中涉及过的或相关联的&#xff09; 微积分和概率与统计由此进&#xff1a;基础数学&#xff1a;微积分和概率与统计-CSDN博客 图论与信息论由此进&#xff1a;基础数学&#xff1a;图论与信息论-CSDN博…