对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

Context Configuration with Environment Profiles 使用环境配置文件的上下文配置

Spring框架对环境和配置文件(又称"bean定义配置文件")提供了一流支持, 集成测试可以配置为在各种测试场景中激活特定的bean定义配置文件。 这可以通过在测试类上添加@ActiveProfiles注解并指定加载测试ApplicationContext时应激活的配置文件列表来实现。spring-doc.cadn.net.cn

您可以将@ActiveProfiles与任何实现了SmartContextLoader SPI的版本一起使用,但在较旧的ContextLoader SPI实现中不支持@ActiveProfiles

考虑两个使用XML配置和@Configuration个类的示例:spring-doc.cadn.net.cn

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="...">

	<bean id="transferService"
			class="com.bank.service.internal.DefaultTransferService">
		<constructor-arg ref="accountRepository"/>
		<constructor-arg ref="feePolicy"/>
	</bean>

	<bean id="accountRepository"
			class="com.bank.repository.internal.JdbcAccountRepository">
		<constructor-arg ref="dataSource"/>
	</bean>

	<bean id="feePolicy"
		class="com.bank.service.internal.ZeroFeePolicy"/>

	<beans profile="dev">
		<jdbc:embedded-database id="dataSource">
			<jdbc:script
				location="classpath:com/bank/config/sql/schema.sql"/>
			<jdbc:script
				location="classpath:com/bank/config/sql/test-data.sql"/>
		</jdbc:embedded-database>
	</beans>

	<beans profile="production">
		<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
	</beans>

	<beans profile="default">
		<jdbc:embedded-database id="dataSource">
			<jdbc:script
				location="classpath:com/bank/config/sql/schema.sql"/>
		</jdbc:embedded-database>
	</beans>

</beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	TransferService transferService;

	@Test
	void testTransferService() {
		// test the transferService
	}
}
@ExtendWith(SpringExtension::class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// test the transferService
	}
}

当运行TransferServiceTest时,其ApplicationContext会从类路径根目录下的app-config.xml配置文件中加载。如果检查app-config.xml,可以看到accountRepository bean依赖于dataSource bean。然而,dataSource并未被定义为顶级bean。相反,dataSource被定义了三次:分别在production配置文件中、dev配置文件以及default配置文件中。spring-doc.cadn.net.cn

通过使用@ActiveProfiles("dev")注解TransferServiceTest,我们指示Spring TestContext框架加载ApplicationContext并将激活配置文件设置为{"dev"}。因此,系统会创建一个嵌入式数据库并填充测试数据,同时accountRepository bean将自动装配开发环境DataSource的引用。这通常是集成测试中所需的效果。spring-doc.cadn.net.cn

有时将bean分配给default profile会很有用。只有当没有其他profile被显式激活时,默认profile中的bean才会被包含。您可以使用此功能定义应用程序默认状态下使用的“回退”bean。例如,您可以显式地为devproduction profile提供数据源,但当两者均未激活时,可将内存数据源定义为默认选项。spring-doc.cadn.net.cn

下面的代码清单演示了如何不使用XML而改用@Configuration个类来实现相同的配置和集成测试:spring-doc.cadn.net.cn

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.addScript("classpath:com/bank/config/sql/test-data.sql")
			.build();
	}
}
@Configuration
@Profile("dev")
class StandaloneDataConfig {

	@Bean
	fun dataSource(): DataSource {
		return EmbeddedDatabaseBuilder()
				.setType(EmbeddedDatabaseType.HSQL)
				.addScript("classpath:com/bank/config/sql/schema.sql")
				.addScript("classpath:com/bank/config/sql/test-data.sql")
				.build()
	}
}
@Configuration
@Profile("production")
public class JndiDataConfig {

	@Bean(destroyMethod="")
	public DataSource dataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}
@Configuration
@Profile("production")
class JndiDataConfig {

	@Bean(destroyMethod = "")
	fun dataSource(): DataSource {
		val ctx = InitialContext()
		return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
	}
}
@Configuration
@Profile("default")
public class DefaultDataConfig {

	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseType.HSQL)
			.addScript("classpath:com/bank/config/sql/schema.sql")
			.build();
	}
}
@Configuration
@Profile("default")
class DefaultDataConfig {

	@Bean
	fun dataSource(): DataSource {
		return EmbeddedDatabaseBuilder()
				.setType(EmbeddedDatabaseType.HSQL)
				.addScript("classpath:com/bank/config/sql/schema.sql")
				.build()
	}
}
@Configuration
public class TransferServiceConfig {

	@Autowired DataSource dataSource;

	@Bean
	public TransferService transferService() {
		return new DefaultTransferService(accountRepository(), feePolicy());
	}

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}

	@Bean
	public FeePolicy feePolicy() {
		return new ZeroFeePolicy();
	}
}
@Configuration
class TransferServiceConfig {

	@Autowired
	lateinit var dataSource: DataSource

	@Bean
	fun transferService(): TransferService {
		return DefaultTransferService(accountRepository(), feePolicy())
	}

	@Bean
	fun accountRepository(): AccountRepository {
		return JdbcAccountRepository(dataSource)
	}

	@Bean
	fun feePolicy(): FeePolicy {
		return ZeroFeePolicy()
	}
}
@SpringJUnitConfig({
		TransferServiceConfig.class,
		StandaloneDataConfig.class,
		JndiDataConfig.class,
		DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	TransferService transferService;

	@Test
	void testTransferService() {
		// test the transferService
	}
}
@SpringJUnitConfig(
		TransferServiceConfig::class,
		StandaloneDataConfig::class,
		JndiDataConfig::class,
		DefaultDataConfig::class)
@ActiveProfiles("dev")
class TransferServiceTest {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// test the transferService
	}
}

在这个变体方案中,我们将XML配置拆分成了四个独立的 @Configuration类:spring-doc.cadn.net.cn

  • TransferServiceConfig: 通过使用@Autowired,依赖注入获取dataSourcespring-doc.cadn.net.cn

  • StandaloneDataConfig: 定义适用于开发者测试的嵌入式数据库的dataSourcespring-doc.cadn.net.cn

  • JndiDataConfig: 定义一个在生产环境中从JNDI获取的dataSourcespring-doc.cadn.net.cn

  • DefaultDataConfig: 定义一个dataSource用于默认嵌入式数据库,当没有激活配置文件时。spring-doc.cadn.net.cn

与基于XML的配置示例类似,我们仍然使用@ActiveProfiles("dev")TransferServiceTest进行注解, 但这次我们通过@ContextConfiguration注解指定了所有四个配置类。 测试类本身的主体部分则保持完全不变。spring-doc.cadn.net.cn

在给定项目中,多个测试类共用一组配置文件是常见场景。因此,为避免重复声明@ActiveProfiles注解,可在基类上声明@ActiveProfiles一次,子类便会自动从基类继承@ActiveProfiles配置。以下示例将@ActiveProfiles(及其他注解)的声明移至抽象基类AbstractIntegrationTestspring-doc.cadn.net.cn

从 Spring Framework 5.3 开始,测试配置也可以从外围类继承。有关详细信息,请参阅 @Nested 测试类配置
@SpringJUnitConfig({
		TransferServiceConfig.class,
		StandaloneDataConfig.class,
		JndiDataConfig.class,
		DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
@SpringJUnitConfig(
		TransferServiceConfig::class,
		StandaloneDataConfig::class,
		JndiDataConfig::class,
		DefaultDataConfig::class)
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

	@Autowired
	TransferService transferService;

	@Test
	void testTransferService() {
		// test the transferService
	}
}
// "dev" profile inherited from superclass
class TransferServiceTest : AbstractIntegrationTest() {

	@Autowired
	lateinit var transferService: TransferService

	@Test
	fun testTransferService() {
		// test the transferService
	}
}

@ActiveProfiles 同样支持一个 inheritProfiles 属性,该属性可用于禁用激活配置集的继承关系,如下示例所示:spring-doc.cadn.net.cn

// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {
	// test body
}
// "dev" profile overridden with "production"
@ActiveProfiles("production", inheritProfiles = false)
class ProductionTransferServiceTest : AbstractIntegrationTest() {
	// test body
}

此外,有时需要通过编程方式而非声明式方式解析测试的活动配置文件——例如基于:spring-doc.cadn.net.cn

要以编程方式解析活动bean定义配置文件,您可以实现一个自定义的ActiveProfilesResolver,并通过使用@ActiveProfilesresolver属性进行注册。更多信息,请参阅相应的 javadoc。 以下示例演示了如何实现和注册自定义的 OperatingSystemActiveProfilesResolverspring-doc.cadn.net.cn

// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
		resolver = OperatingSystemActiveProfilesResolver.class,
		inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
	// test body
}
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
		resolver = OperatingSystemActiveProfilesResolver::class,
		inheritProfiles = false)
class TransferServiceTest : AbstractIntegrationTest() {
	// test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

	@Override
	public String[] resolve(Class<?> testClass) {
		String profile = ...;
		// determine the value of profile based on the operating system
		return new String[] {profile};
	}
}
class OperatingSystemActiveProfilesResolver : ActiveProfilesResolver {

	override fun resolve(testClass: Class<*>): Array<String> {
		val profile: String = ...
		// determine the value of profile based on the operating system
		return arrayOf(profile)
	}
}