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

如何避免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套件,在套件层面启动和关闭服务器:

  1. 首先创建测试套件类:
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();
    }
}
  1. 修改SftpTestConfig的Bean定义,直接返回套件创建的实例:
@TestConfiguration
public class SftpTestConfig {
    @Bean
    public EmbeddedSftpServer embeddedSftpServer() {
        return AllIntegrationTestSuite.SFTP_SERVER;
    }
    // 其他Bean定义
}

运行测试时直接执行该套件即可,服务器只会在套件启动时初始化一次,套件所有测试跑完后销毁。

内容的提问来源于stack exchange,提问作者user17375469

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 05:15:00