如何避免Spring Bean在带@SpringBatchTest的子测试上下文中二次实例化
问题根因
@SpringBatchTest注解会向Spring测试上下文注入一批Spring Batch专属的配置Bean,导致该测试类的上下文缓存Key和另外两个仅用@ExtendWith(SpringExtension.class)的测试类不一致,Spring会为其创建全新的独立上下文,因此会重新初始化一次EmbeddedSftpServer Bean,触发生命周期方法。
解决方案(按改造成本从低到高排序)
方案1:统一测试上下文配置(改造成本最低)
直接给ExampleOneIT和ExampleTwoIT也加上@SpringBatchTest注解即可,该注解本身已经集成了@ExtendWith(SpringExtension.class),无需重复声明。修改后三个测试类的上下文配置完全一致,会复用同一个Spring上下文,EmbeddedSftpServer自然只会初始化一次。
@SpringBatchTest @ContextConfiguration(classes = SftpTestConfig.class) class ExampleOneIT { @Autowired private EmbeddedSftpServer embeddedSftpServer; } @SpringBatchTest @ContextConfiguration(classes = SftpTestConfig.class) class ExampleTwoIT { @Autowired private EmbeddedSftpServer embeddedSftpServer; }
方案2:将SFTP服务器实现为JVM级单例(适配多上下文场景)
如果不想给非Batch测试加@SpringBatchTest,可以修改EmbeddedSftpServer的实现,用静态变量控制生命周期,确保不管Spring创建多少个Bean实例,服务器只启动一次,且JVM退出时才关闭:
public class EmbeddedSftpServer implements InitializingBean, DisposableBean { // 标记服务器是否已经启动 private static final AtomicBoolean SERVER_RUNNING = new AtomicBoolean(false); // 持有全局唯一实例 private static EmbeddedSftpServer GLOBAL_INSTANCE; public EmbeddedSftpServer() { if (GLOBAL_INSTANCE == null) { GLOBAL_INSTANCE = this; } } @Override public void afterPropertiesSet() throws Exception { // CAS保证启动逻辑仅执行一次 if (SERVER_RUNNING.compareAndSet(false, true)) { // 原有启动服务器的代码放在此处 } } @Override public void destroy() throws Exception { // 注册JVM关闭钩子,所有测试跑完JVM退出时才关闭服务器 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (SERVER_RUNNING.compareAndSet(true, false)) { // 原有关闭服务器的代码放在此处 } })); } // 其他类内容 }
方案3:用JUnit测试套件统一管理SFTP生命周期(完全脱离Spring管控)
如果希望完全由测试生命周期管控服务器启停,可将所有测试归入同一个JUnit套件,在套件层面启动和关闭服务器:
- 首先创建测试套件类:
import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; @Suite @SelectClasses({ExampleOneIT.class, ExampleTwoIT.class, ExampleThreeIT.class}) public class AllIntegrationTestSuite { private static EmbeddedSftpServer SFTP_SERVER; @BeforeAll static void initSftpServer() throws Exception { SFTP_SERVER = new EmbeddedSftpServer(); SFTP_SERVER.afterPropertiesSet(); } @AfterAll static void shutdownSftpServer() throws Exception { SFTP_SERVER.destroy(); } }
- 修改
SftpTestConfig的Bean定义,直接返回套件创建的实例:
@TestConfiguration public class SftpTestConfig { @Bean public EmbeddedSftpServer embeddedSftpServer() { return AllIntegrationTestSuite.SFTP_SERVER; } // 其他Bean定义 }
运行测试时直接执行该套件即可,服务器只会在套件启动时初始化一次,套件所有测试跑完后销毁。
内容的提问来源于stack exchange,提问作者user17375469
相关产品推荐
相关产品推荐

