Skip to content

Commit e7b52cf

Browse files
committed
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
1 parent 78028cd commit e7b52cf

15 files changed

+429
-301
lines changed

framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc

+76-53
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,51 @@
22
= Context Configuration with Dynamic Property Sources
33

44
The Spring TestContext Framework provides support for _dynamic_ properties via the
5-
`@DynamicPropertySource` annotation and the `DynamicPropertyRegistry`.
5+
`DynamicPropertyRegistry`, the `@DynamicPropertySource` annotation, and the
6+
`DynamicPropertyRegistrar` API.
67

78
[NOTE]
89
====
9-
The `@DynamicPropertySource` annotation and its supporting infrastructure were originally
10-
designed to allow properties from {testcontainers-site}[Testcontainers] based tests to be
11-
exposed easily to Spring integration tests. However, this feature may be used with any
12-
form of external resource whose lifecycle is managed outside the test's
13-
`ApplicationContext` or with beans whose lifecycle is managed by the test's
14-
`ApplicationContext`.
10+
The dynamic property source infrastructure was originally designed to allow properties
11+
from {testcontainers-site}[Testcontainers] based tests to be exposed easily to Spring
12+
integration tests. However, these features may be used with any form of external resource
13+
whose lifecycle is managed outside the test's `ApplicationContext` or with beans whose
14+
lifecycle is managed by the test's `ApplicationContext`.
1515
====
1616

17-
In contrast to the
18-
xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
19-
annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
20-
`static` methods in integration test classes or to `@Bean` methods in test
21-
`@Configuration` classes in order to add properties with dynamic values to the set of
22-
`PropertySources` in the `Environment` for the `ApplicationContext` loaded for the
23-
integration test.
17+
18+
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
19+
== Precedence
20+
21+
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
22+
the operating system's environment, Java system properties, or property sources added by
23+
the application declaratively by using `@PropertySource` or programmatically. Thus,
24+
dynamic properties can be used to selectively override properties loaded via
25+
`@TestPropertySource`, system property sources, and application property sources.
26+
27+
28+
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-registry]]
29+
== `DynamicPropertyRegistry`
2430

2531
A `DynamicPropertyRegistry` is used to add _name-value_ pairs to the `Environment`.
2632
Values are dynamic and provided via a `Supplier` which is only invoked when the property
27-
is resolved. Typically, method references are used to supply values.
33+
is resolved. Typically, method references are used to supply values. The following
34+
sections provide examples of how to use the `DynamicPropertyRegistry`.
2835

29-
Methods in integration test classes that are annotated with `@DynamicPropertySource` must
30-
be `static` and must accept a single `DynamicPropertyRegistry` argument.
3136

32-
`@Bean` methods annotated with `@DynamicPropertySource` may either accept an argument of
33-
type `DynamicPropertyRegistry` or access a `DynamicPropertyRegistry` instance autowired
34-
into their enclosing `@Configuration` class. Note, however, that `@Bean` methods which
35-
interact with a `DynamicPropertyRegistry` are not required to be annotated with
36-
`@DynamicPropertySource` unless they need to enforce eager initialization of the bean
37-
within the context. See the class-level javadoc for `DynamicPropertyRegistry` for details.
37+
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-source]]
38+
== `@DynamicPropertySource`
39+
40+
In contrast to the
41+
xref:testing/testcontext-framework/ctx-management/property-sources.adoc[`@TestPropertySource`]
42+
annotation that is applied at the class level, `@DynamicPropertySource` can be applied to
43+
`static` methods in integration test classes in order to add properties with dynamic
44+
values to the set of `PropertySources` in the `Environment` for the `ApplicationContext`
45+
loaded for the integration test.
46+
47+
Methods in integration test classes that are annotated with `@DynamicPropertySource` must
48+
be `static` and must accept a single `DynamicPropertyRegistry` argument. See the
49+
class-level javadoc for `DynamicPropertyRegistry` for further details.
3850

3951
[TIP]
4052
====
@@ -107,11 +119,33 @@ Kotlin::
107119
----
108120
======
109121

110-
The following example demonstrates how to use `DynamicPropertyRegistry` and
111-
`@DynamicPropertySource` with a `@Bean` method. The `api.url` property can be accessed
112-
via Spring's `Environment` abstraction or injected directly into other Spring-managed
113-
components – for example, via `@Value("${api.url}")`. The value of the `api.url` property
114-
will be dynamically retrieved from the `ApiServer` bean.
122+
123+
[[testcontext-ctx-management-dynamic-property-sources-dynamic-property-registrar]]
124+
== `DynamicPropertyRegistrar`
125+
126+
As an alternative to implementing `@DynamicPropertySource` methods in integration test
127+
classes, you can register implementations of the `DynamicPropertyRegistrar` API as beans
128+
within the test's `ApplicationContext`. Doing so allows you to support additional use
129+
cases that are not possible with a `@DynamicPropertySource` method. For example, since a
130+
`DynamicPropertyRegistrar` is itself a bean in the `ApplicationContext`, it can interact
131+
with other beans in the context and register dynamic properties that are sourced from
132+
those beans.
133+
134+
Any bean in a test's `ApplicationContext` that implements the `DynamicPropertyRegistrar`
135+
interface will be automatically detected and eagerly initialized before the singleton
136+
pre-instantiation phase, and the `accept()` methods of such beans will be invoked with a
137+
`DynamicPropertyRegistry` that performs the actual dynamic property registration on
138+
behalf of the registrar.
139+
140+
WARNING: Any interaction with other beans results in eager initialization of those other
141+
beans and their dependencies.
142+
143+
The following example demonstrates how to implement a `DynamicPropertyRegistrar` as a
144+
lambda expression that registers a dynamic property for the `ApiServer` bean. The
145+
`api.url` property can be accessed via Spring's `Environment` abstraction or injected
146+
directly into other Spring-managed components – for example, via `@Value("${api.url}")`,
147+
and the value of the `api.url` property will be dynamically retrieved from the
148+
`ApiServer` bean.
115149

116150
[tabs]
117151
======
@@ -123,11 +157,13 @@ Java::
123157
class TestConfig {
124158
125159
@Bean
126-
@DynamicPropertySource
127-
ApiServer apiServer(DynamicPropertyRegistry registry) {
128-
ApiServer apiServer = new ApiServer();
129-
registry.add("api.url", apiServer::getUrl);
130-
return apiServer;
160+
ApiServer apiServer() {
161+
return new ApiServer();
162+
}
163+
164+
@Bean
165+
DynamicPropertyRegistrar apiServerProperties(ApiServer apiServer) {
166+
return registry -> registry.add("api.url", apiServer::getUrl);
131167
}
132168
}
133169
----
@@ -140,27 +176,14 @@ Kotlin::
140176
class TestConfig {
141177
142178
@Bean
143-
@DynamicPropertySource
144-
fun apiServer(registry: DynamicPropertyRegistry): ApiServer {
145-
val apiServer = ApiServer()
146-
registry.add("api.url", apiServer::getUrl)
147-
return apiServer
179+
fun apiServer(): ApiServer {
180+
return ApiServer()
181+
}
182+
183+
@Bean
184+
fun apiServerProperties(apiServer: ApiServer): DynamicPropertyRegistrar {
185+
return registry -> registry.add("api.url", apiServer::getUrl)
148186
}
149187
}
150188
----
151189
======
152-
153-
NOTE: The use of `@DynamicPropertySource` on the `@Bean` method is optional and results
154-
in the `ApiServer` bean being eagerly initialized so that other beans in the context can
155-
be given access to the dynamic properties sourced from the `ApiServer` bean when those
156-
other beans are initialized.
157-
158-
[[testcontext-ctx-management-dynamic-property-sources-precedence]]
159-
== Precedence
160-
161-
Dynamic properties have higher precedence than those loaded from `@TestPropertySource`,
162-
the operating system's environment, Java system properties, or property sources added by
163-
the application declaratively by using `@PropertySource` or programmatically. Thus,
164-
dynamic properties can be used to selectively override properties loaded via
165-
`@TestPropertySource`, system property sources, and application property sources.
166-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context;
18+
19+
/**
20+
* Registrar that is used to add properties with dynamically resolved values to
21+
* the {@code Environment} via a {@link DynamicPropertyRegistry}.
22+
*
23+
* <p>Any bean in a test's {@code ApplicationContext} that implements the
24+
* {@code DynamicPropertyRegistrar} interface will be automatically detected and
25+
* eagerly initialized before the singleton pre-instantiation phase, and the
26+
* {@link #accept} methods of such beans will be invoked with a
27+
* {@code DynamicPropertyRegistry} that performs the actual dynamic property
28+
* registration on behalf of the registrar.
29+
*
30+
* <p>This is an alternative to implementing
31+
* {@link DynamicPropertySource @DynamicPropertySource} methods in integration
32+
* test classes and supports additional use cases that are not possible with a
33+
* {@code @DynamicPropertySource} method. For example, since a
34+
* {@code DynamicPropertyRegistrar} is itself a bean in the {@code ApplicationContext},
35+
* it can interact with other beans in the context and register dynamic properties
36+
* that are sourced from those beans. Note, however, that any interaction with
37+
* other beans results in eager initialization of those other beans and their
38+
* dependencies.
39+
*
40+
* <h3>Precedence</h3>
41+
*
42+
* <p>Dynamic properties have higher precedence than those loaded from
43+
* {@link TestPropertySource @TestPropertySource}, the operating system's
44+
* environment, Java system properties, or property sources added by the
45+
* application declaratively by using
46+
* {@link org.springframework.context.annotation.PropertySource @PropertySource}
47+
* or programmatically. Thus, dynamic properties can be used to selectively
48+
* override properties loaded via {@code @TestPropertySource}, system property
49+
* sources, and application property sources.
50+
*
51+
* <h3>Example</h3>
52+
*
53+
* <p>The following example demonstrates how to implement a
54+
* {@code DynamicPropertyRegistrar} as a lambda expression that registers a
55+
* dynamic property for the {@code ApiServer} bean. Other beans in the
56+
* {@code ApplicationContext} can access the {@code api.url} property which is
57+
* dynamically retrieved from the {@code ApiServer} bean &mdash; for example,
58+
* via {@code @Value("${api.url}")}.
59+
*
60+
* <pre class="code">
61+
* &#064;Configuration
62+
* class TestConfig {
63+
*
64+
* &#064;Bean
65+
* ApiServer apiServer() {
66+
* return new ApiServer();
67+
* }
68+
*
69+
* &#064;Bean
70+
* DynamicPropertyRegistrar apiServerProperties(ApiServer apiServer) {
71+
* return registry -> registry.add("api.url", apiServer::getUrl);
72+
* }
73+
*
74+
* }</pre>
75+
*
76+
* @author Sam Brannen
77+
* @since 6.2
78+
* @see DynamicPropertySource
79+
* @see DynamicPropertyRegistry
80+
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()
81+
*/
82+
@FunctionalInterface
83+
public interface DynamicPropertyRegistrar {
84+
85+
void accept(DynamicPropertyRegistry registry);
86+
87+
}

spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistry.java

+7-11
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,18 @@
2727
* test classes.
2828
*
2929
* <p>As of Spring Framework 6.2, a {@code DynamicPropertyRegistry} is also
30-
* registered as a singleton bean in the test's {@code ApplicationContext}. This
31-
* allows a {@code DynamicPropertyRegistry} to be autowired into a
32-
* {@code @Configuration} class or supplied to a {@code @Bean} method as an
33-
* argument, making it possible to register a dynamic property from within a test's
34-
* {@code ApplicationContext}. For example, a {@code @Bean} method can register
35-
* a property whose value is dynamically sourced from the bean that the method
36-
* returns. Note that such a {@code @Bean} method can optionally be annotated
37-
* with {@code @DynamicPropertySource} to enforce eager initialization of the
38-
* bean within the context, thereby ensuring that any dynamic properties sourced
39-
* from that bean are available to other singleton beans within the context.
40-
* See {@link DynamicPropertySource @DynamicPropertySource} for an example.
30+
* supplied to {@link DynamicPropertyRegistrar} beans in the test's
31+
* {@code ApplicationContext}, making it possible to register dynamic properties
32+
* based on beans in the context. For example, a {@code @Bean} method can return
33+
* a {@code DynamicPropertyRegistrar} that registers a property whose value is
34+
* dynamically sourced from another bean in the context. See the documentation
35+
* for {@code DynamicPropertyRegistrar} for an example.
4136
*
4237
* @author Phillip Webb
4338
* @author Sam Brannen
4439
* @since 5.2.5
4540
* @see DynamicPropertySource
41+
* @see DynamicPropertyRegistrar
4642
*/
4743
public interface DynamicPropertyRegistry {
4844

spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java

+18-41
Original file line numberDiff line numberDiff line change
@@ -24,42 +24,35 @@
2424

2525
/**
2626
* {@code @DynamicPropertySource} is an annotation that can be applied to static
27-
* methods in integration test classes or to {@code @Bean} methods in test
28-
* {@code @Configuration} classes in order to add properties with dynamic values
29-
* to the {@code Environment}'s set of {@code PropertySources}.
27+
* methods in integration test classes in order to add properties with dynamic
28+
* values to the {@code Environment}'s set of {@code PropertySources}.
29+
*
30+
* <p>Alternatively, dynamic properties can be added to the {@code Environment}
31+
* by special beans in the test's {@code ApplicationContext}. See
32+
* {@link DynamicPropertyRegistrar} for details.
3033
*
3134
* <p>This annotation and its supporting infrastructure were originally designed
3235
* to allow properties from
3336
* <a href="https://www.testcontainers.org/">Testcontainers</a> based tests to be
3437
* exposed easily to Spring integration tests. However, this feature may be used
3538
* with any form of external resource whose lifecycle is managed outside the
36-
* test's {@code ApplicationContext} or with beans whose lifecycle is managed by
37-
* the test's {@code ApplicationContext}.
39+
* test's {@code ApplicationContext}.
3840
*
39-
* <p>{@code @DynamicPropertySource}-annotated methods use a
40-
* {@code DynamicPropertyRegistry} to add <em>name-value</em> pairs to the
41-
* {@code Environment}'s set of {@code PropertySources}. Values are dynamic and
42-
* provided via a {@link java.util.function.Supplier} which is only invoked when
43-
* the property is resolved. Typically, method references are used to supply values,
44-
* as in the example below.
41+
* <p>{@code @DynamicPropertySource} methods use a {@link DynamicPropertyRegistry}
42+
* to add <em>name-value</em> pairs to the {@code Environment}'s set of
43+
* {@code PropertySources}. Values are dynamic and provided via a
44+
* {@link java.util.function.Supplier} which is only invoked when the property is
45+
* resolved. Typically, method references are used to supply values, as in the
46+
* example below.
4547
*
4648
* <p>Methods in integration test classes that are annotated with
4749
* {@code @DynamicPropertySource} must be {@code static} and must accept a single
48-
* {@link DynamicPropertyRegistry} argument.
49-
*
50-
* <p>{@code @Bean} methods annotated with {@code @DynamicPropertySource} may
51-
* either accept an argument of type {@code DynamicPropertyRegistry} or access a
52-
* {@code DynamicPropertyRegistry} instance autowired into their enclosing
53-
* {@code @Configuration} class. Note, however, that {@code @Bean} methods which
54-
* interact with a {@code DynamicPropertyRegistry} are not required to be annotated
55-
* with {@code @DynamicPropertySource} unless they need to enforce eager
56-
* initialization of the bean within the context.
57-
* See {@link DynamicPropertyRegistry} for details.
50+
* {@code DynamicPropertyRegistry} argument.
5851
*
5952
* <p>Dynamic properties from methods annotated with {@code @DynamicPropertySource}
6053
* will be <em>inherited</em> from enclosing test classes, analogous to inheritance
61-
* from superclasses and interfaces.
62-
* See {@link NestedTestConfiguration @NestedTestConfiguration} for details.
54+
* from superclasses and interfaces. See
55+
* {@link NestedTestConfiguration @NestedTestConfiguration} for details.
6356
*
6457
* <p><strong>NOTE</strong>: if you use {@code @DynamicPropertySource} in a base
6558
* class and discover that tests in subclasses fail because the dynamic properties
@@ -69,6 +62,7 @@
6962
* correct dynamic properties.
7063
*
7164
* <h3>Precedence</h3>
65+
*
7266
* <p>Dynamic properties have higher precedence than those loaded from
7367
* {@link TestPropertySource @TestPropertySource}, the operating system's
7468
* environment, Java system properties, or property sources added by the
@@ -103,28 +97,11 @@
10397
* }
10498
* }</pre>
10599
*
106-
* <p>The following example demonstrates how to use {@code @DynamicPropertySource}
107-
* with a {@code @Bean} method. Beans in the {@code ApplicationContext} can
108-
* access the {@code api.url} property which is dynamically retrieved from the
109-
* {@code ApiServer} bean.
110-
*
111-
* <pre class="code">
112-
* &#064;Configuration
113-
* class TestConfig {
114-
*
115-
* &#064;Bean
116-
* &#064;DynamicPropertySource
117-
* ApiServer apiServer(DynamicPropertyRegistry registry) {
118-
* ApiServer apiServer = new ApiServer();
119-
* registry.add("api.url", apiServer::getUrl);
120-
* return apiServer;
121-
* }
122-
* }</pre>
123-
*
124100
* @author Phillip Webb
125101
* @author Sam Brannen
126102
* @since 5.2.5
127103
* @see DynamicPropertyRegistry
104+
* @see DynamicPropertyRegistrar
128105
* @see ContextConfiguration
129106
* @see TestPropertySource
130107
* @see org.springframework.core.env.PropertySource

0 commit comments

Comments
 (0)