JUnit 5 通过 @ExtendWith 和扩展接口实现拦截效果:BeforeEachCallback、AfterEachCallback、AroundTestExecutionCallback 等回调接口可在测试生命周期各阶段插入逻辑,其中 AroundTestExecutionCallback 支持环绕拦截,如耗时统计;可组合多个扩展形成拦截链,并支持自定义注解按需启用,扩展间通过 ExtensionContext.Store 共享状态。

JUnit 5 本身没有“拦截器”这个内置概念,但通过 @ExtendWith + 扩展接口(Extension),可以非常自然地实现类似 AOP 的拦截效果——在测试方法执行前、后或环绕时插入逻辑。
核心思路:用回调接口替代“拦截”
不是靠代理或字节码增强,而是利用 JUnit 5 的生命周期回调机制。你不需要写代理类,只需实现对应接口,在特定时机被自动调用:
- BeforeEachCallback:每个测试方法执行前调用,适合初始化、清空缓存、开启事务
- AfterEachCallback:每个测试方法执行后调用,适合回滚、释放资源、断言清理状态
- AroundTestExecutionCallback:最接近“环绕拦截”,能完全控制测试方法是否执行、何时执行、是否捕获异常,适合耗时统计、异常统一处理、上下文隔离
- BeforeAllCallback / AfterAllCallback:整个测试类前后各执行一次,适合启动/关闭数据库、加载配置
写一个带耗时统计的环绕拦截扩展
这是最典型的“拦截器式”用法。它会在每个测试方法外层套一层逻辑,就像 try-finally:
public class TimingExtension implements AroundTestExecutionCallback {
@Override
public void aroundTestExecution(Executable executable, ExtensionContext context) throws Throwable {
long start = System.currentTimeMillis();
try {
executable.execute(); // 实际执行测试方法
} finally {
long cost = System.currentTimeMillis() - start;
String testName = context.getDisplayName();
System.out.printf("[Timing] %s took %d ms%n", testName, cost);
}
}
}
使用时只需加注解:
@ExtendWith(TimingExtension.class)
class MyTest {
@Test
void testSomething() {
// 会被自动计时
}
}
组合多个扩展,形成拦截链
JUnit 5 支持同时启用多个扩展,它们按声明顺序依次生效(注意:不是所有扩展都支持叠加,需看具体实现):
- 比如先用
DatabaseCleanExtension清库,再用TimingExtension计时,最后用LogExtension打印日志 - 写法:
@ExtendWith({DatabaseCleanExtension.class, TimingExtension.class, LogExtension.class}) - 每个扩展只关注一件事,职责清晰,也方便复用和开关
让扩展支持参数化(更灵活的“拦截器”)
如果想控制哪些测试才启用某拦截逻辑,可以用自定义注解配合 ExecutionCondition 或直接在扩展中读取测试方法上的注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithTransaction { }
<p>public class TransactionExtension implements AroundTestExecutionCallback {
@Override
public void aroundTestExecution(Executable executable, ExtensionContext context) throws Throwable {
Method method = context.getRequiredTestMethod();
boolean needTx = method.isAnnotationPresent(WithTransaction.class);
if (needTx) {
beginTransaction();
try {
executable.execute();
commitTransaction();
} catch (Throwable t) {
rollbackTransaction();
throw t;
}
} else {
executable.execute();
}
}
}</p>
这样就能按需启用事务拦截,而不是全局强制:
@ExtendWith(TransactionExtension.class)
class UserServiceTest {
@Test
@WithTransaction
void shouldCreateUser() { /* 自动包裹事务 */ }
<pre class="brush:php;toolbar:false;">@Test
void shouldValidateEmail() { /* 不走事务 */ }}
不复杂但容易忽略:扩展是无状态的,如需跨方法共享数据(比如事务连接),要用 ExtensionContext.Store 存储上下文;另外,AroundTestExecutionCallback 是唯一能真正“拦截并控制执行流程”的接口,其他回调不能阻止测试运行。











