Spring中JUnit测试类的自定义@Verify注解切面无法生效问题求助
大家好,我是Spring新手,最近折腾了一个需求跟踪的小功能:自定义了@Satisfy和@Verify两个注解,用来关联业务代码的需求实现和对应的测试用例,最终是想通过切面调用GitLab API来标记需求的「已实现」和「已验证」状态。
目前的情况有点头疼:
- 标记在业务方法上的
@Satisfy注解对应的切面完全正常工作,执行方法时会触发切面逻辑 - 但标记在JUnit测试方法上的
@Verify注解完全没反应,我查了一整天资料,怀疑是不是切面根本无法作用在测试用例上?
下面是我的全部代码,麻烦各位帮忙看看问题出在哪,怎么才能让@Verify的切面在测试方法执行时被触发?
自定义注解代码
@Satisfy注解
package com.example.demo; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Satisfy { String requirementId() default "No requirementId provided"; }
@Verify注解
package com.example.demo; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Verify { String requirementId() default "No requirementId provided"; }
切面代码
SatisfyAspect(正常生效)
package com.example.demo; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class SatisfyAspect { @Around("@annotation(com.example.demo.Satisfy)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("ENTER SatisfyAspect"); Object proceed = joinPoint.proceed(); return proceed; } }
VerifyAspect(未生效)
package com.example.demo; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Aspect @Component public class VerifyAspect { @Around("@annotation(com.example.demo.Verify)") public Object verify(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("ENTER VerifyAspect"); Object proceed = joinPoint.proceed(); return proceed; } }
业务代码类
package com.example.demo; import org.springframework.stereotype.Component; @Component public class Addition { @Satisfy(requirementId="12345678-1") int add(int a, int b) { return a + b; } }
JUnit测试类
package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { @Autowired private Addition addition; @Test void contextLoads() { } @Test @Verify(requirementId="12345678-1") void testAdd() { assert(addition.add(1, 2) == 3); } }
我尝试过的思路和疑问
我自己猜的是:Spring AOP的切面只能作用于Spring容器管理的Bean,而JUnit测试类的实例是由测试引擎创建的,不是Spring容器里的Bean,所以测试方法的调用没经过Spring代理,切面就拦截不到?那有没有办法让测试类的方法也能被切面拦截到呢?
问题原因分析
你猜的完全没错!Spring AOP默认是基于动态代理实现的,只能拦截Spring容器中管理的Bean的方法调用。而JUnit测试类的实例是由JUnit测试引擎直接创建的,不属于Spring容器的Bean范畴,所以测试方法的调用不会经过Spring的代理逻辑,自然触发不了对应的切面。
解决方案
这里给你两种可行的解决思路:
方案1:把测试逻辑抽成Spring Bean,通过Bean调用触发切面
既然Spring AOP只管容器内的Bean,那我们可以把测试代码的核心逻辑放到一个被Spring管理的Bean中,然后在测试方法里调用这个Bean的方法,这样@Verify注解标记在这个Bean的方法上就能被切面拦截了:
- 新建一个测试用的Spring Bean类:
package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class RequirementTestExecutor { @Autowired private Addition addition; @Verify(requirementId="12345678-1") public void executeAddTest() { assert(addition.add(1, 2) == 3); } }
- 修改测试类,注入这个Bean并调用方法:
package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class DemoApplicationTests { @Autowired private RequirementTestExecutor testExecutor; @Test void contextLoads() { } @Test void testAdd() { testExecutor.executeAddTest(); // 调用Bean的方法,触发@Verify切面 } }
这样执行测试时,executeAddTest方法是通过Spring代理Bean调用的,切面就能正常拦截了。
方案2:使用AspectJ加载时织入(LTW)替代Spring AOP
如果你不想重构测试代码,也可以直接用原生AspectJ的加载时织入功能,它可以不依赖Spring代理,直接在类加载时修改字节码,这样不管是业务类还是测试类的方法,都能被切面拦截。
- 首先在
pom.xml中添加AspectJ LTW的依赖(Maven项目):
<dependencies> <!-- 其他依赖保持不变 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <scope>test</scope> </dependency> </dependencies>
- 在测试资源目录下(
src/test/resources)新建META-INF/aop.xml配置文件,指定要织入的切面:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver> <!-- 织入指定包下的所有类,包括测试类 --> <include within="com.example.demo..*"/> </weaver> <aspects> <!-- 指定要生效的切面类 --> <aspect name="com.example.demo.VerifyAspect"/> <aspect name="com.example.demo.SatisfyAspect"/> </aspects> </aspectj>
- 在测试类上添加
@EnableLoadTimeWeaving注解,或者在@SpringBootTest中指定AOP模式:
@SpringBootTest(properties = "spring.aop.mode=aspectj") @EnableLoadTimeWeaving class DemoApplicationTests { // 原测试代码完全不用改 }
这样配置后,AspectJ会在类加载时直接修改测试类的字节码,@Verify注解的切面就能正常触发了。
总结
两种方案各有优劣:
- 方案1更轻量,不需要额外配置,适合小范围的测试场景
- 方案2更灵活,不需要修改测试代码结构,适合需要大面积在测试中使用切面的场景
你可以根据自己的实际需求选择合适的方案~
内容来源于stack exchange

