如果某些 bean 仅在测试模式下存在,如何以正确的顺序初始化 bean?

How to initialize beans in proper order if some beans are present in test mode only?

美好的一天。我的 Spring 启动应用程序使用 Postgress 数据库。对于测试,它使用 H2 数据库。当运行在非测试模式bean需要按以下顺序初始化:

1) Init DataSource
2) Init JPA beans

当运行处于测试模式时,我需要在 JPA bean 初始化之前创建并填充 H2 数据库:

1) Init DataSource
2) Init DataSourceInitializer
3) Init JPA beans

问题是 JPA bean 在 DataSourceInitializer 之前被初始化(第 3 步在第 2 步之前)并且缺少表时测试失败(休眠。hbm2ddl.auto=validate)。

步骤 1

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Primary
    @Bean
    @ConfigurationProperties(prefix = "datasource.runtime")
    public DataSource runtimeDataSource() {
        return DataSourceBuilder.create().build();
    }
}

步骤 2

@Configuration
@Profile(Profiles.INTEGRATION_TEST)
public class DataSourceTestConfig {

    @Autowired
    private ResourceLoader resourceLoader;

    @Bean
    public DataSourceInitializer runtimeDataSourceInitializer(@Qualifier("runtimeDataSource") DataSource dataSource) {
        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);
        initializer.setDatabasePopulator(new ResourceDatabasePopulator(
                resourceLoader.getResource("classpath:runtime/schema.sql")
        ));
        return initializer;
    }
}

第 3 步

@Configuration
@EnableTransactionManagement
public class JpaConfig {

    @Autowired
    private Environment environment;

    @Autowired
    @Qualifier(value = "runtimeDataSource")
    private DataSource runtimeDataSource;

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(runtimeDataSource)
                .properties(hibernateSettings())
                .packages(
                        "cz.adx.anx.car.cases.domain",
                        "cz.adx.anx.car.lib.domain",
                        "org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
                )
                .persistenceUnit("runtimePersistenceUnit")
                .build();
    }
}

我需要来自 class DataSourceTestConfig 的 bean 在 JpaConfig 之前和 DataSourceConfig 之后初始化,但仅限于测试模式。在非测试模式下,来自 JpaConfig 的 bean 应该在 DataSourceConfig 之后初始化,并且必须省略来自 DataSourceTestConfig 的 bean。因此,我无法使用来自 class DataSourceTestConfig 的 @DependsOn bean 注释 class JpaConfig,因为此 class 位于测试包中,而不存在于非测试模式中。我可以复制配置 classes 并使它们以配置文件为条件,但我对这个解决方案感到不舒服。请问,有更好的解决办法吗?提前致谢!

PS:我的应用程序使用两个 databases/datasources,但我缩短了上面的代码以使其更易于阅读。我正在使用 Spring Boot 1.3.1.RELEASE.

更新 1: 我尝试使用@luboskrnac 建议的方法。我在我的集​​成测试中放置了注释 ActiveProfiles classes:

@ActiveProfiles("IT")
public abstract class IntegrationTest {...}

并且我在 class JpaConfig 中的相关 bean 上使用了注释 Profile,如下所示:

@Configuration
@EnableTransactionManagement
public class JpaConfig {

    @Autowired
    private Environment environment;

    @Autowired
    @Qualifier(value = "runtimeDataSource")
    private DataSource runtimeDataSource;

    @Autowired
    @Qualifier(value = "configDataSource")
    private DataSource configDataSource;

    @Profile("!IT")
    @Bean(name = "runtimeEntityManagerFactory")
    @DependsOn("runtimeDataSource")
    public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return createRuntimeEntityManagerFactory(builder);
    }

    @Profile("IT")
    @Bean(name = "runtimeEntityManagerFactory")
    @DependsOn("runtimeDataSourceInitializer")
    public LocalContainerEntityManagerFactoryBean testRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return jpaConfig.createRuntimeEntityManagerFactory(builder);
    }

    public LocalContainerEntityManagerFactoryBean createRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
            .dataSource(runtimeDataSource)
            .properties(hibernateSettings())
            .packages(
                "cz.adx.anx.car.cases.domain",
                "cz.adx.anx.car.lib.domain",
                "org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
            )
            .persistenceUnit("runtimePersistenceUnit")
            .build();
    }
}

我正在以同样的方式创建事务管理器。因为我使用两个数据源(两个不同的数据库),所以我在 EnableJpaRepositories 注释中使用了 bean 名称。

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "runtimeEntityManagerFactory",
        transactionManagerRef = "runtimeTransactionManager",
        basePackages = "cz.adx.anx.car.lib.repository"
)
public class JpaCarLibRepositoryConfig {
}

所以需要非测试bean和测试bean同名注册。但是 Spring 给了我一个例外:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: runtimeEntityManagerFactory

有什么建议吗?

我建议放弃任何关于显式 bean 创建顺序或 bean 依赖性的考虑。

根据Spring @Sql annotation简单地在测试中填充数据库。测试可能看起来像这样:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@Sql("/test-schema.sql")
public class DatabaseTests {

    @Test
    public void emptySchemaTest {
        // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    public void userTest {
        // execute code that uses the test schema and test data
    }
}

如果您需要交换数据源(例如,在 PROD 中使用 PostgereSQL,在测试中使用 H2),只需使用 Spring @Profile@ActiveProfiles 注释。