Introduce DynamicPropertyRegistrar to replace DynamicPropertyRegistry bean
Spring Boot's testing support registers a DynamicPropertyRegistry as a bean in the ApplicationContext, which conflicts with the DynamicPropertyRegistry registered as a bean by the Spring TestContext Framework (TCF) since Spring Framework 6.2 M2. To avoid that conflict and to improve the user experience for Spring's testing support, this commit introduces a DynamicPropertyRegistrar API to replace the DynamicPropertyRegistry bean support. Specifically, the TCF no longer registers a DynamicPropertyRegistry as a bean in the ApplicationContext. Instead, users can now register custom implementations of DynamicPropertyRegistrar as beans in the ApplicationContext, and the DynamicPropertiesContextCustomizer now registers a DynamicPropertyRegistrarBeanInitializer which eagerly initializes DynamicPropertyRegistrar beans and invokes their accept() methods with an appropriate DynamicPropertyRegistry. In addition, a singleton DynamicValuesPropertySource is created and registered with the Environment for use in DynamicPropertiesContextCustomizer and DynamicPropertyRegistrarBeanInitializer, which allows @DynamicPropertySource methods and DynamicPropertyRegistrar beans to transparently populate the same DynamicValuesPropertySource. Closes gh-33501
This commit is contained in:
@@ -2,39 +2,51 @@
|
||||
= Context Configuration with Dynamic Property Sources
|
||||
|
||||
The Spring TestContext Framework provides support for _dynamic_ properties via the
|
||||
`@DynamicPropertySource` annotation and the `DynamicPropertyRegistry`.
|
||||
`DynamicPropertyRegistry`, the `@DynamicPropertySource` annotation, and the
|
||||
`DynamicPropertyRegistrar` API.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `@DynamicPropertySource` annotation and its supporting infrastructure were originally
|
||||
designed to allow properties from {testcontainers-site}[Testcontainers] based tests to be
|
||||
exposed easily to Spring integration tests. However, this feature may be used with any
|
||||
form of external resource whose lifecycle is managed outside the test's
|
||||
`ApplicationContext` or with beans whose lifecycle is managed by the test's
|
||||
`ApplicationContext`.
|
||||
The dynamic property source infrastructure was originally designed to allow properties
|
||||
from {testcontainers-site}[Testcontainers] based tests to be exposed easily to Spring
|
||||
integration tests. However, these features may be used with any form of external resource
|
||||
whose lifecycle is managed outside the test's `ApplicationContext` or with beans whose
|
||||
lifecycle is managed by the test's `ApplicationContext`.
|
||||
====
|
||||
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
|
||||
== Precedence
|
||||
|
||||
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
|
||||
the operating system's environment, Java system properties, or property sources added by
|
||||
the application declaratively by using `@PropertySource` or programmatically. Thus,
|
||||
dynamic properties can be used to selectively override properties loaded via
|
||||
`@TestPropertySource`, system property sources, and application property sources.
|
||||
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-registry]]
|
||||
== `DynamicPropertyRegistry`
|
||||
|
||||
A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
|
||||
Values are dynamic and provided via a `Supplier` which is only invoked when the property
|
||||
is resolved. Typically, method references are used to supply values. The following
|
||||
sections provide examples of how to use the `DynamicPropertyRegistry`.
|
||||
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-source]]
|
||||
== `@DynamicPropertySource`
|
||||
|
||||
In contrast to the
|
||||
xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
|
||||
annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
|
||||
`static` methods in integration test classes or to `@Bean` methods in test
|
||||
`@Configuration` classes in order to add properties with dynamic values to the set of
|
||||
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
|
||||
integration test.
|
||||
|
||||
A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
|
||||
Values are dynamic and provided via a `Supplier` which is only invoked when the property
|
||||
is resolved. Typically, method references are used to supply values.
|
||||
`static` methods in integration test classes in order to add properties with dynamic
|
||||
values to the set of `PropertySources` in the `Environment` for the `ApplicationContext`
|
||||
loaded for the integration test.
|
||||
|
||||
Methods in integration test classes that are annotated with `@DynamicPropertySource` must
|
||||
be `static` and must accept a single `DynamicPropertyRegistry` argument.
|
||||
|
||||
`@Bean` methods annotated with `@DynamicPropertySource` may either accept an argument of
|
||||
type `DynamicPropertyRegistry` or access a `DynamicPropertyRegistry` instance autowired
|
||||
into their enclosing `@Configuration` class. Note, however, that `@Bean` methods which
|
||||
interact with a `DynamicPropertyRegistry` are not required to be annotated with
|
||||
`@DynamicPropertySource` unless they need to enforce eager initialization of the bean
|
||||
within the context. See the class-level javadoc for `DynamicPropertyRegistry` for details.
|
||||
be `static` and must accept a single `DynamicPropertyRegistry` argument. See the
|
||||
class-level javadoc for `DynamicPropertyRegistry` for further details.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
@@ -107,11 +119,33 @@ Kotlin::
|
||||
----
|
||||
======
|
||||
|
||||
The following example demonstrates how to use `DynamicPropertyRegistry` and
|
||||
`@DynamicPropertySource` with a `@Bean` method. The `api.url` property can be accessed
|
||||
via Spring's `Environment` abstraction or injected directly into other Spring-managed
|
||||
components – for example, via `@Value("${api.url}")`. The value of the `api.url` property
|
||||
will be dynamically retrieved from the `ApiServer` bean.
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-registrar]]
|
||||
== `DynamicPropertyRegistrar`
|
||||
|
||||
As an alternative to implementing `@DynamicPropertySource` methods in integration test
|
||||
classes, you can register implementations of the `DynamicPropertyRegistrar` API as beans
|
||||
within the test's `ApplicationContext`. Doing so allows you to support additional use
|
||||
cases that are not possible with a `@DynamicPropertySource` method. For example, since a
|
||||
`DynamicPropertyRegistrar` is itself a bean in the `ApplicationContext`, it can interact
|
||||
with other beans in the context and register dynamic properties that are sourced from
|
||||
those beans.
|
||||
|
||||
Any bean in a test's `ApplicationContext` that implements the `DynamicPropertyRegistrar`
|
||||
interface will be automatically detected and eagerly initialized before the singleton
|
||||
pre-instantiation phase, and the `accept()` methods of such beans will be invoked with a
|
||||
`DynamicPropertyRegistry` that performs the actual dynamic property registration on
|
||||
behalf of the registrar.
|
||||
|
||||
WARNING: Any interaction with other beans results in eager initialization of those other
|
||||
beans and their dependencies.
|
||||
|
||||
The following example demonstrates how to implement a `DynamicPropertyRegistrar` as a
|
||||
lambda expression that registers a dynamic property for the `ApiServer` bean. The
|
||||
`api.url` property can be accessed via Spring's `Environment` abstraction or injected
|
||||
directly into other Spring-managed components – for example, via `@Value("${api.url}")`,
|
||||
and the value of the `api.url` property will be dynamically retrieved from the
|
||||
`ApiServer` bean.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -123,11 +157,13 @@ Java::
|
||||
class TestConfig {
|
||||
|
||||
@Bean
|
||||
@DynamicPropertySource
|
||||
ApiServer apiServer(DynamicPropertyRegistry registry) {
|
||||
ApiServer apiServer = new ApiServer();
|
||||
registry.add("api.url", apiServer::getUrl);
|
||||
return apiServer;
|
||||
ApiServer apiServer() {
|
||||
return new ApiServer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
DynamicPropertyRegistrar apiServerProperties(ApiServer apiServer) {
|
||||
return registry -> registry.add("api.url", apiServer::getUrl);
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -140,27 +176,14 @@ Kotlin::
|
||||
class TestConfig {
|
||||
|
||||
@Bean
|
||||
@DynamicPropertySource
|
||||
fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
|
||||
val apiServer = ApiServer()
|
||||
registry.add("api.url", apiServer::getUrl)
|
||||
return apiServer
|
||||
fun apiServer(): ApiServer {
|
||||
return ApiServer()
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun apiServerProperties(apiServer: ApiServer): DynamicPropertyRegistrar {
|
||||
return registry -> registry.add("api.url", apiServer::getUrl)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
NOTE: The use of `@DynamicPropertySource` on the `@Bean` method is optional and results
|
||||
in the `ApiServer` bean being eagerly initialized so that other beans in the context can
|
||||
be given access to the dynamic properties sourced from the `ApiServer` bean when those
|
||||
other beans are initialized.
|
||||
|
||||
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
|
||||
== Precedence
|
||||
|
||||
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
|
||||
the operating system's environment, Java system properties, or property sources added by
|
||||
the application declaratively by using `@PropertySource` or programmatically. Thus,
|
||||
dynamic properties can be used to selectively override properties loaded via
|
||||
`@TestPropertySource`, system property sources, and application property sources.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user