使用动态属性源进行上下文配置

Spring TestContext 框架通过 DynamicPropertyRegistry@DynamicPropertySource 注解以及 DynamicPropertyRegistrar API 提供对动态属性的支持。spring-doc.cadn.net.cn

动态属性源基础设施最初是为了让基于 Testcontainers 的测试能够轻松地将属性暴露给 Spring 集成测试而设计的。然而,这些功能也可用于任何生命周期在测试的 ApplicationContext 之外管理的外部资源,或用于生命周期由测试的 ApplicationContext 管理的 Bean。spring-doc.cadn.net.cn

优先级

动态属性的优先级高于通过 @TestPropertySource、操作系统环境变量、Java 系统属性,或通过 @PropertySource 声明式地或以编程方式由应用程序添加的属性源所加载的属性。因此,动态属性可用于有选择地覆盖通过 @TestPropertySource、系统属性源和应用程序属性源加载的属性。spring-doc.cadn.net.cn

DynamicPropertyRegistry

DynamicPropertyRegistry 用于向 Environment 中添加名称-值对。 这些值是动态的,通过一个 Supplier 提供,仅在解析该属性时才会调用该 DynamicPropertyRegistry。 通常使用方法引用来提供值。以下各节提供了如何使用 4 的示例。spring-doc.cadn.net.cn

@DynamicPropertySource

与应用于类级别的 @TestPropertySource 注解不同,@DynamicPropertySource 可以应用于集成测试类中的 static 方法,以便将具有动态值的属性添加到为集成测试加载的 ApplicationContextEnvironment 中的 PropertySources 集合中。spring-doc.cadn.net.cn

在集成测试类中,使用 @DynamicPropertySource 注解的方法必须是 static 的,并且必须接受一个 DynamicPropertyRegistry 类型的参数。更多详细信息,请参见 DynamicPropertyRegistry 类级别的 Javadoc。spring-doc.cadn.net.cn

如果您在基类中使用 @DynamicPropertySource,并发现子类中的测试因动态属性在子类之间发生变化而失败,则可能需要使用 @DirtiesContext 注解您的基类,以确保每个子类都获得具有正确动态属性的自有 ApplicationContextspring-doc.cadn.net.cn

以下示例使用 Testcontainers 项目在 Spring ApplicationContext 之外管理一个 Redis 容器。所管理的 Redis 容器的 IP 地址和端口通过 ApplicationContextredis.host 属性提供给测试用例的 redis.port 中的组件。这些属性可以通过 Spring 的 Environment 抽象层访问,也可以直接注入到由 Spring 管理的组件中——例如,分别通过 @Value("${redis.host}")@Value("${redis.port}") 注入。spring-doc.cadn.net.cn

@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

	@Container
	static GenericContainer redis =
		new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);

	@DynamicPropertySource
	static void redisProperties(DynamicPropertyRegistry registry) {
		registry.add("redis.host", redis::getHost);
		registry.add("redis.port", redis::getFirstMappedPort);
	}

	// tests ...

}
@SpringJUnitConfig(/* ... */)
@Testcontainers
class ExampleIntegrationTests {

	companion object {

		@Container
		@JvmStatic
		val redis: GenericContainer =
			GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379)

		@DynamicPropertySource
		@JvmStatic
		fun redisProperties(registry: DynamicPropertyRegistry) {
			registry.add("redis.host", redis::getHost)
			registry.add("redis.port", redis::getFirstMappedPort)
		}
	}

	// tests ...

}

DynamicPropertyRegistrar

作为在集成测试类中实现 @DynamicPropertySource 方法的替代方案,您可以将 DynamicPropertyRegistrar API 的实现注册为测试 ApplicationContext 中的 Bean。这样做可以支持一些使用 @DynamicPropertySource 方法无法实现的额外用例。例如,由于 DynamicPropertyRegistrar 本身是 ApplicationContext 中的一个 Bean,它可以与其他上下文中的 Bean 进行交互,并注册来源于这些 Bean 的动态属性。spring-doc.cadn.net.cn

测试的 ApplicationContext 中任何实现了 DynamicPropertyRegistrar 接口的 Bean 都会被自动检测到,并在单例预实例化阶段之前被提前初始化,同时这些 Bean 的 accept() 方法将被调用,并传入一个 DynamicPropertyRegistry,该注册表会代表注册器执行实际的动态属性注册。spring-doc.cadn.net.cn

与其它 Bean 的任何交互都会导致这些 Bean 及其依赖项被提前初始化。

以下示例演示了如何将 DynamicPropertyRegistrar 实现为一个 lambda 表达式,用于为 ApiServer bean 注册一个动态属性。api.url 属性可以通过 Spring 的 Environment 抽象来访问,也可以直接注入到其他由 Spring 管理的组件中——例如,通过 @Value("${api.url}") 注入,而 api.url 属性的值将从 ApiServer bean 中动态获取。spring-doc.cadn.net.cn

@Configuration
class TestConfig {

	@Bean
	ApiServer apiServer() {
		return new ApiServer();
	}

	@Bean
	DynamicPropertyRegistrar apiPropertiesRegistrar(ApiServer apiServer) {
		return registry -> registry.add("api.url", apiServer::getUrl);
	}
}
@Configuration
class TestConfig {

	@Bean
	fun apiServer(): ApiServer {
		return ApiServer()
	}

	@Bean
	fun apiPropertiesRegistrar(apiServer: ApiServer): DynamicPropertyRegistrar {
		return registry -> registry.add("api.url", apiServer::getUrl)
	}
}