Spring 采用纯注解实现 AOP 切面增强
Spring 的 Aop 切面编程的主要用途是:在不改变相关方法原有代码的情况下,实现对相关方法的功能增强,其本质就是采用动态代理技术来实现的。有关 Spring 的 Aop 底层原理所采用的动态代理技术,我将在下篇博客进行介绍。
本篇博客主要介绍 Spring 如何采用纯注解的方式,对相关方法进行 Aop 扩展增强。有关 Spring 的 Aop 的相关术语,这里不进行详细介绍,网上资料很多,限于篇幅有限,这里仅仅介绍如果进行快速搭建和使用,在本篇博客的最后会提供 Demo 的源代码。
一、搭建工程
新建一个 maven 项目,导入相关 jar 包,我导入的都是最新版本的 jar 包,内容如下:
有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。
<dependencies> <!--Spring 的基础核心 jar 包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.17</version> </dependency> <!--第三方 Aop 切面编程 jar 包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.8</version> </dependency> <!-- 由于本 Demo 需要使用 junit 进行单元测试, 因此需要导入 junit 和 spring-test 这两个 jar 包, 用于 spring 整合 junit 单元测试 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.17</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。
搭建好的项目工程整体目录比较简单,具体如下图所示:
项目工程结构简单介绍:
aop 包下存放 Aop 的方法类,这些方法用于对相关 Service 类中的方法增强
service 包下存放的是业务处理实现类
AopApp 这个是该 demo 的 main 入口所在类,使用 Spring 访问数据库,验证搭建成果
test 目录下 EmployeeAopTest 这个是测试方法类,里面编写测试 Service 接口的方法,验证 Aop 的执行情况
二、Service 业务方法的细节
本 Demo 中只有一个接口 EmployeeService 和一个实现类 EmployeeServiceImpl ,里面的内容非常简单。
需要注意的是:
Service 的业务方法类最好要实现接口,因为在实际开发场景中,Aop 切面配置的位置,最佳方案是配置在接口上,而不是配置到具体的实现类上。这样的好处在于后续如果接口有其它的实现类,Aop 切面增强功能也在接口的其它实现类上自动生效。
package com.jobs.service; public interface EmployeeService { //不会报错的方法 Integer normalTest(Integer xxx, Integer yyy); //会出现异常的方法 void errorTest(Integer zzz); //测试方法 String aopTest(String ppp, String qqq); }
package com.jobs.service.impl; import com.jobs.service.EmployeeService; import org.springframework.stereotype.Service; //通过 @Service 注解将该实现类的实例化对象,装载到 Spring 容器中,方便后续注入使用 @Service("empService") public class EmployeeServiceImpl implements EmployeeService { //不会报错的方法,测试 Aop 方法执行 @Override public Integer normalTest(Integer xxx, Integer yyy) { Integer result = xxx + yyy; System.out.println("normalTest 方法执行了,计算结果为:" + result); return result; } //会出现异常的方法,测试 Aop 方法执行 @Override public void errorTest(Integer zzz) { System.out.println("errorTest 方法执行了,必然会抛出异常..."); //这里必然会抛出异常 Integer result = zzz / 0; } //测试方法,测试 Aop 方法执行 @Override public String aopTest(String ppp, String qqq) { String result = ppp + "-------" + qqq; System.out.println("aopTest 方法执行了,结果为:" + result); return result; } }
本 Demo 的业务方法,只有 3 个,实现内容也非常简单。
normalTest 方法用来展示 Aop 在【被增强的方法】正常不报错情况下的执行顺序。
errorTest 方法用于展示 Aop 在【被增强的方法】出错的情况下的执行顺序。
aopTest 方法用于在本 Demo 的启动类 AopApp 中,验证本 Demo 的 Aop 功能是否搭建成功。
三、Spring 纯注解配置 Aop 细节
编写 Aop 功能的方法类 AopAdvice ,该类中的方法用于在不改变 Service 中方法的情况下,强行介入到具体方法的执行过程中,实现对 Service 方法的增强。AopAdvice 类的具体内容如下:
package com.jobs.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; //使用 @Component 注解将 AopAdvice 的实例化对象,装载到 Spring 容器中 //使用 @Aspect 注解表示该类是切面类,里面提供了切面增强的方法 @Component @Aspect public class AopAdvice { //使用 @Pointcut 注解,设置要为哪些类的哪些方法进行切面方法增强 //这里设置的是,针对 com.jobs.service 包下的所有 Service 类中的方法进行增强 @Pointcut("execution(* com.jobs.service.*Service.*(..))") public void pt() { } //使用 @Before 注解,表示在【被增强的方法】之前执行下面的代码 //该注解修饰的方法,可以有一个 JoinPoint 参数,用于获取【被增强的方法】的相关信息 @Before("pt()") public void before(JoinPoint jp) { System.out.println("前置 before 方法执行了..."); //获取所执行方法的签名信息 Signature signature = jp.getSignature(); System.out.println("\t 方法签名为:" + signature); //通过签名获取执行接口名称或类名 String interfaceName = signature.getDeclaringTypeName(); System.out.println("\t 方法所属接口或所属类名为:" + interfaceName); //通过签名获取执行方法名称 String methodName = signature.getName(); System.out.println("\t 方法名为:" + methodName); //获取方法的所传入的参数值 Object[] args = jp.getArgs(); for (Object obj : args) { System.out.println("\t 方法传入的参数值:" + obj); } } //使用 @After 注解,表示在【被增强的方法】之后执行下面的代码 //无论【被增强的方法】在执行过程中,是否出错抛出异常,都会执行下面的代码 //该注解修饰的方法,可以有一个 JoinPoint 参数,用于获取【被增强的方法】的相关信息 @After("pt()") public void after(JoinPoint jp) { //后置方法,通过 jp 参数,也能够获取到所执行方法的相关信息 System.out.println("后置 after 方法执行了..."); } //使用 @AfterReturning 注解,表示在【被增强的方法】返回结果后,执行下面的代码 //如果【被增强的方法】在执行过程中,是否出错抛出异常,将不会执行下面的代码 //该注解修饰的方法,可以有一个 Object 参数,用于获取【被增强的方法】执行后的返回值 @AfterReturning(pointcut = "pt()", returning = "ret") public void afterReturing(Object ret) { //通过参数,可以获取到方法执行的结果 System.out.println("返回结果后 afterReturing 执行了,返回结果为:" + ret); } //使用 @AfterThrowing 注解,表示【被增强的方法】出错抛异常后,执行下面的代码 //如果【被增强的方法】没有出错抛异常,将不会执行下面的代码 //该注解修饰的方法,可以有一个 Throwable 参数,用于获取【被增强的方法】出错时的异常对象 @AfterThrowing(pointcut = "pt()", throwing = "t") public void afterThrowing(Throwable t) { //通过参数,可以获取到异常对象,从而获取异常信息 System.out.println("抛出异常后 afterThrowing 执行了,异常信息为:" + t.getMessage()); } //使用 @Around 注解,可以在【被增强的方法】执行前后,增加相关代码 // @Around 注解是我们以后用的最多的注解,因为其功能强大灵活。 //该注解修饰的方法,可以有一个 ProceedingJoinPoint 参数,用于获取【被增强的方法】的相关信息 //可以通过 ProceedingJoinPoint 参数的 proceed() 来执行【被增强的方法】 //需要注意的是:如果这里没有对【被增强的方法】进行 try catch 包裹处理异常, //那么【被增强的方法】如果出现异常,将不会执行后面的代码,这样后置增强代码就不会执行了 @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前 around before 执行了..."); //在环绕方法中,可以获取到方法执行的结果 Object ret = pjp.proceed(); System.out.println("环绕后 around after 执行了..."); return ret; } }
下面我们创建一个 Spring 配置类 SpringConfig 并启用 Aop 功能,具体内容如下:
package com.jobs.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; // @Configuration 注解,表示该类是 Spring 的配置类 // @ComponentScan 注解,表示扫描具体包及其子包下的所有注解,将相关对象实例化并装载到 Spring 容器中 // @EnableAspectJAutoProxy 这个就是启用 Aop 功能的注解 @Configuration @ComponentScan("com.jobs") @EnableAspectJAutoProxy public class SpringConfig { }
下面我们创建一个拥有 main 方法的入口类,调用 EmployeeService 接口中的 aopTest 方法,验证搭建成果。
package com.jobs; import com.jobs.config.SpringConfig; import com.jobs.service.EmployeeService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class AopApp { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class); EmployeeService empService = (EmployeeService) ctx.getBean("empService"); empService.aopTest("侯胖胖","任肥肥"); } }
因为 aopTest 方法内部不会出异常,所以不会执行 Aop 的切面类 AopAdvice 下 @AfterThrowing 注解修饰的方法,其它注解的方法都会正常执行,从而实现对 aopTest 的增强。具体如下图所示:
本 Demo 把 Aop 的所有注解方法都用上了,目的在于演示 Aop 各种注解增强后的方法,具体的执行顺序。
从上图的执行结果可以发现:
@Around 注解修饰的方法(环绕通知),
其内部【被增强方法之前的代码】在【最前面】执行,【被增强方法之后的代码】在【最后面】执行。
(上图中的红色框输出的内容)
@Before 注解修饰的方法(前置通知),
其实际执行顺序仅次于【环绕通知】的【被增强方法之前的代码】。
(上图中的蓝色框输出的内容)
绿色框内输出的内容,是【被增强方法】执行时输出的,其实际执行顺序仅次于【前置通知】。
(上图中的绿色框输出的内容)
@AfterReturning 注解修饰的方法(返回结果通知),其执行顺序仅次于【被增强的方法】。
(上图中的紫色框输出的内容)
@After 注解修饰的方法(后置通知),在【返回结果通知】之后执行。
(上图中的黑色框输出的内容)
四、测试异常方法 Aop 执行顺序
前面的博客已经介绍过 Spring 如何整合 junit ,这里就不再详细介绍了,下面列出编写的测试类内容:
package com.jobs; import com.jobs.config.SpringConfig; import com.jobs.service.EmployeeService; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class EmployeeAopTest { @Autowired private EmployeeService employeeService; @Test public void normalTest() { int result = employeeService.normalTest(120, 130); //Assert.assertEquals 主要用来验证【执行结果】与【所期望的结果】是否一致 Assert.assertEquals(250, result); } @Test public void errorTest() { try { //此方法执行会抛出异常,从而导致 AfterThrowing 执行 employeeService.errorTest(38); } catch (Exception ex) { System.out.println("单元测试方法获得的异常信息:" + ex.getMessage()); } } @Test public void aopTest() { String result = employeeService.aopTest("候肥肥", "任胖胖"); //Assert.assertEquals 主要用来验证【执行结果】与【所期望的结果】是否一致 Assert.assertEquals("候肥肥-------任胖胖", result); } }
有关 normalTest 方法测试的 Aop 执行顺序,跟 aopTest 方法的测试结果一样,因为都是没有报错的正常方法。
下面列出 errorTest 方法测试的 Aop 执行顺序,employeeService.errorTest 方法会抛异常,其执行结果如下:
从上图中可以发现,当【被增强的方法】在执行过程中出错抛出异常,Aop 的执行顺序为:
@Around 注解修饰的方法(环绕通知),
其内部【被增强方法之前的代码】在【最前面】执行,【被增强方法之后的代码】不会被执行。
(上图中的红色框输出的内容)
@Before 注解修饰的方法(前置通知),
其实际执行顺序仅次于【环绕通知】的【被增强方法之前的代码】。
(上图中的蓝色框输出的内容)
绿色框内输出的内容,是【被增强方法】执行时输出的,其实际执行顺序仅次于【前置通知】。
(上图中的绿色框输出的内容)
@AfterReturning 注解修饰的方法(返回结果通知),
当【被增强的方法】在执行过程中出错抛出异常时,不会被执行。
@AfterThrowing 注解修饰的方法(异常通知),
当【被增强的方法】在执行过程中出错抛出异常时,会被执行。
(上图中的紫色框输出的内容)
@After 注解修饰的方法(后置通知),
无论【被增强的方法】在执行过程中是否出错抛出异常时,都会执行。
(上图中的黑色框输出的内容)
上图中黄色框中的内容,是单元测试方法打印出来的。
到此为止,已经完成了 Spring 有关 Aop 切面编程快速搭建和使用的介绍,大家可以根据实际情况在工作中进行参考使用。限于篇幅有限,这里没有介绍具体的 Aop 概念和细节。在实际开发场景中,使用最多的是 Aop 的环绕通知,其它 Aop 切面通知很少使用。
最后提供本 Demo 的源代码:https://files.cnblogs.com/files/blogs/699532/spring_aop_junit.zip
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构