You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中JUnit测试类的自定义@Verify注解切面无法生效问题求助

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的方法上就能被切面拦截了:

  1. 新建一个测试用的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);
    }
}
  1. 修改测试类,注入这个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代理,直接在类加载时修改字节码,这样不管是业务类还是测试类的方法,都能被切面拦截。

  1. 首先在pom.xml中添加AspectJ LTW的依赖(Maven项目):
<dependencies>
    <!-- 其他依赖保持不变 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. 在测试资源目录下(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>
  1. 在测试类上添加@EnableLoadTimeWeaving注解,或者在@SpringBootTest中指定AOP模式:
@SpringBootTest(properties = "spring.aop.mode=aspectj")
@EnableLoadTimeWeaving
class DemoApplicationTests {
    // 原测试代码完全不用改
}

这样配置后,AspectJ会在类加载时直接修改测试类的字节码,@Verify注解的切面就能正常触发了。

总结

两种方案各有优劣:

  • 方案1更轻量,不需要额外配置,适合小范围的测试场景
  • 方案2更灵活,不需要修改测试代码结构,适合需要大面积在测试中使用切面的场景

你可以根据自己的实际需求选择合适的方案~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:54:34