跳到主要内容

使用环境配置文件进行上下文配置

DeepSeek V3 中英对照 Context Configuration with Environment Profiles

Spring 框架对环境和配置(也称为“bean 定义配置”)提供了一流的支持,集成测试可以配置为在各种测试场景中激活特定的 bean 定义配置。这是通过使用 @ActiveProfiles 注解标注测试类并提供在加载测试的 ApplicationContext 时应激活的配置列表来实现的。

备注

你可以将 @ActiveProfiles 与任何实现了 SmartContextLoader SPI 的实现一起使用,但对于较旧的 ContextLoader SPI 实现,@ActiveProfiles 并不被支持。

考虑两个使用 XML 配置和 @Configuration 类的示例:

<!-- 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>
xml
@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
}
}
java

当运行 TransferServiceTest 时,它的 ApplicationContext 是从类路径根目录下的 app-config.xml 配置文件中加载的。如果你检查 app-config.xml,可以看到 accountRepository bean 依赖于一个 dataSource bean。然而,dataSource 并没有被定义为一个顶层的 bean。相反,dataSource 被定义了三次:在 production 配置文件中,在 dev 配置文件中,以及在 default 配置文件中。

通过在 TransferServiceTest 上添加 @ActiveProfiles("dev") 注解,我们指示 Spring TestContext Framework 在加载 ApplicationContext 时将激活的配置文件设置为 {"dev"}。因此,系统会创建一个嵌入式数据库并填充测试数据,同时 accountRepository bean 会被注入一个指向开发环境 DataSource 的引用。这通常是我们希望在集成测试中实现的效果。

有时将 bean 分配到 default 配置文件是很有用的。只有在没有明确激活其他配置文件时,才会包含 default 配置文件中的 bean。你可以使用这种方式来定义在应用程序默认状态下使用的“备用”bean。例如,你可以为 devproduction 配置文件显式提供一个数据源,但在这些配置文件都未激活时,将内存数据源定义为默认数据源。

以下代码清单展示了如何使用 @Configuration 类而不是 XML 来实现相同的配置和集成测试:

@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();
}
}
java
@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");
}
}
java
@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();
}
}
java
@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();
}
}
java
@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {

@Autowired
TransferService transferService;

@Test
void testTransferService() {
// test the transferService
}
}
java

在这个变体中,我们将 XML 配置拆分为了四个独立的 @Configuration 类:

  • TransferServiceConfig:通过使用 @Autowired 依赖注入获取 dataSource

  • StandaloneDataConfig:为适合开发者测试的嵌入式数据库定义 dataSource

  • JndiDataConfig:定义从生产环境中的 JNDI 获取的 dataSource

  • DefaultDataConfig:在没有激活任何配置文件的情况下,为默认的嵌入式数据库定义 dataSource

与基于 XML 的配置示例一样,我们仍然使用 @ActiveProfiles("dev") 注解 TransferServiceTest,但这次我们通过使用 @ContextConfiguration 注解指定了所有四个配置类。测试类的主体本身保持完全不变。

通常情况下,在一个给定的项目中,单个配置文件集会被多个测试类使用。因此,为了避免重复声明 @ActiveProfiles 注解,你可以在一个基类上声明一次 @ActiveProfiles,子类会自动从基类继承 @ActiveProfiles 配置。在下面的例子中,@ActiveProfiles(以及其他注解)的声明被移动到了一个抽象超类 AbstractIntegrationTest 中:

备注

测试配置也可以从封闭类中继承。详情请参见 @Nested 测试类配置

@SpringJUnitConfig({
TransferServiceConfig.class,
StandaloneDataConfig.class,
JndiDataConfig.class,
DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
java
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {

@Autowired
TransferService transferService;

@Test
void testTransferService() {
// test the transferService
}
}
java

@ActiveProfiles 还支持一个 inheritProfiles 属性,可用于禁用活动配置文件的继承,如下例所示:

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

此外,有时需要以编程方式而非声明式地解析测试的活动配置文件——例如,基于:

  • 当前的操作系统。

  • 是否在持续集成构建服务器上运行测试。

  • 某些环境变量的存在。

  • 自定义类级别注解的存在。

  • 其他关注点。

要以编程方式解析活动 bean 定义配置文件,你可以实现一个自定义的 ActiveProfilesResolver,并通过使用 @ActiveProfilesresolver 属性来注册它。更多信息,请参阅相应的 javadoc。以下示例展示了如何实现并注册一个自定义的 OperatingSystemActiveProfilesResolver

// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
java
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};
}
}
java