diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
index 6678e834c58..ca26bfa32d5 100644
--- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
+++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/dynamic-property-sources.adoc
@@ -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.
-
diff --git a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistrar.java b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistrar.java
new file mode 100644
index 00000000000..e30fe648054
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertyRegistrar.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+/**
+ * Registrar that is used to add properties with dynamically resolved values to
+ * the {@code Environment} via a {@link DynamicPropertyRegistry}.
+ *
+ *
Any bean in a test's {@code ApplicationContext} that implements the
+ * {@code DynamicPropertyRegistrar} interface will be automatically detected and
+ * eagerly initialized before the singleton pre-instantiation phase, and the
+ * {@link #accept} methods of such beans will be invoked with a
+ * {@code DynamicPropertyRegistry} that performs the actual dynamic property
+ * registration on behalf of the registrar.
+ *
+ *
This is an alternative to implementing
+ * {@link DynamicPropertySource @DynamicPropertySource} methods in integration
+ * test classes and supports additional use cases that are not possible with a
+ * {@code @DynamicPropertySource} method. For example, since a
+ * {@code DynamicPropertyRegistrar} is itself a bean in the {@code ApplicationContext},
+ * it can interact with other beans in the context and register dynamic properties
+ * that are sourced from those beans. Note, however, that any interaction with
+ * other beans results in eager initialization of those other beans and their
+ * dependencies.
+ *
+ *
Precedence
+ *
+ *
Dynamic properties have higher precedence than those loaded from
+ * {@link TestPropertySource @TestPropertySource}, the operating system's
+ * environment, Java system properties, or property sources added by the
+ * application declaratively by using
+ * {@link org.springframework.context.annotation.PropertySource @PropertySource}
+ * or programmatically. Thus, dynamic properties can be used to selectively
+ * override properties loaded via {@code @TestPropertySource}, system property
+ * sources, and application property sources.
+ *
+ *
Example
+ *
+ *
The following example demonstrates how to implement a
+ * {@code DynamicPropertyRegistrar} as a lambda expression that registers a
+ * dynamic property for the {@code ApiServer} bean. Other beans in the
+ * {@code ApplicationContext} can access the {@code api.url} property which is
+ * dynamically retrieved from the {@code ApiServer} bean — for example,
+ * via {@code @Value("${api.url}")}.
+ *
+ *
As of Spring Framework 6.2, a {@code DynamicPropertyRegistry} is also
- * registered as a singleton bean in the test's {@code ApplicationContext}. This
- * allows a {@code DynamicPropertyRegistry} to be autowired into a
- * {@code @Configuration} class or supplied to a {@code @Bean} method as an
- * argument, making it possible to register a dynamic property from within a test's
- * {@code ApplicationContext}. For example, a {@code @Bean} method can register
- * a property whose value is dynamically sourced from the bean that the method
- * returns. Note that such a {@code @Bean} method can optionally be annotated
- * with {@code @DynamicPropertySource} to enforce eager initialization of the
- * bean within the context, thereby ensuring that any dynamic properties sourced
- * from that bean are available to other singleton beans within the context.
- * See {@link DynamicPropertySource @DynamicPropertySource} for an example.
+ * supplied to {@link DynamicPropertyRegistrar} beans in the test's
+ * {@code ApplicationContext}, making it possible to register dynamic properties
+ * based on beans in the context. For example, a {@code @Bean} method can return
+ * a {@code DynamicPropertyRegistrar} that registers a property whose value is
+ * dynamically sourced from another bean in the context. See the documentation
+ * for {@code DynamicPropertyRegistrar} for an example.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2.5
* @see DynamicPropertySource
+ * @see DynamicPropertyRegistrar
*/
public interface DynamicPropertyRegistry {
diff --git a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java
index a491abe3949..919569d025d 100644
--- a/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java
+++ b/spring-test/src/main/java/org/springframework/test/context/DynamicPropertySource.java
@@ -24,42 +24,35 @@ import java.lang.annotation.Target;
/**
* {@code @DynamicPropertySource} is an annotation that can be applied to static
- * methods in integration test classes or to {@code @Bean} methods in test
- * {@code @Configuration} classes in order to add properties with dynamic values
- * to the {@code Environment}'s set of {@code PropertySources}.
+ * methods in integration test classes in order to add properties with dynamic
+ * values to the {@code Environment}'s set of {@code PropertySources}.
+ *
+ *
Alternatively, dynamic properties can be added to the {@code Environment}
+ * by special beans in the test's {@code ApplicationContext}. See
+ * {@link DynamicPropertyRegistrar} for details.
*
*
This annotation and its supporting infrastructure were originally designed
* to allow properties from
* 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 {@code ApplicationContext} or with beans whose lifecycle is managed by
- * the test's {@code ApplicationContext}.
+ * test's {@code ApplicationContext}.
*
- *
{@code @DynamicPropertySource}-annotated methods use a
- * {@code DynamicPropertyRegistry} to add name-value pairs to the
- * {@code Environment}'s set of {@code PropertySources}. Values are dynamic and
- * provided via a {@link java.util.function.Supplier} which is only invoked when
- * the property is resolved. Typically, method references are used to supply values,
- * as in the example below.
+ *
{@code @DynamicPropertySource} methods use a {@link DynamicPropertyRegistry}
+ * to add name-value pairs to the {@code Environment}'s set of
+ * {@code PropertySources}. Values are dynamic and provided via a
+ * {@link java.util.function.Supplier} which is only invoked when the property is
+ * resolved. Typically, method references are used to supply values, as in the
+ * example below.
*
*
Methods in integration test classes that are annotated with
* {@code @DynamicPropertySource} must be {@code static} and must accept a single
- * {@link DynamicPropertyRegistry} argument.
- *
- *
{@code @Bean} methods annotated with {@code @DynamicPropertySource} may
- * either accept an argument of type {@code DynamicPropertyRegistry} or access a
- * {@code DynamicPropertyRegistry} instance autowired into their enclosing
- * {@code @Configuration} class. Note, however, that {@code @Bean} methods which
- * interact with a {@code DynamicPropertyRegistry} are not required to be annotated
- * with {@code @DynamicPropertySource} unless they need to enforce eager
- * initialization of the bean within the context.
- * See {@link DynamicPropertyRegistry} for details.
+ * {@code DynamicPropertyRegistry} argument.
*
*
Dynamic properties from methods annotated with {@code @DynamicPropertySource}
* will be inherited from enclosing test classes, analogous to inheritance
- * from superclasses and interfaces.
- * See {@link NestedTestConfiguration @NestedTestConfiguration} for details.
+ * from superclasses and interfaces. See
+ * {@link NestedTestConfiguration @NestedTestConfiguration} for details.
*
*
NOTE: if you use {@code @DynamicPropertySource} in a base
* class and discover that tests in subclasses fail because the dynamic properties
@@ -69,6 +62,7 @@ import java.lang.annotation.Target;
* correct dynamic properties.
*
*
Precedence
+ *
*
Dynamic properties have higher precedence than those loaded from
* {@link TestPropertySource @TestPropertySource}, the operating system's
* environment, Java system properties, or property sources added by the
@@ -103,28 +97,11 @@ import java.lang.annotation.Target;
* }
* }
*
- *
The following example demonstrates how to use {@code @DynamicPropertySource}
- * with a {@code @Bean} method. Beans in the {@code ApplicationContext} can
- * access the {@code api.url} property which is dynamically retrieved from the
- * {@code ApiServer} bean.
- *
- *
- *
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2.5
* @see DynamicPropertyRegistry
+ * @see DynamicPropertyRegistrar
* @see ContextConfiguration
* @see TestPropertySource
* @see org.springframework.core.env.PropertySource
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultDynamicPropertyRegistry.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultDynamicPropertyRegistry.java
deleted file mode 100644
index e079c379541..00000000000
--- a/spring-test/src/main/java/org/springframework/test/context/support/DefaultDynamicPropertyRegistry.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.context.support;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Supplier;
-
-import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MutablePropertySources;
-import org.springframework.core.env.PropertySource;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.util.Assert;
-
-/**
- * Default {@link DynamicPropertyRegistry} implementation.
- *
- * @author Sam Brannen
- * @since 6.2
- */
-final class DefaultDynamicPropertyRegistry implements DynamicPropertyRegistry {
-
- final Map> valueSuppliers = Collections.synchronizedMap(new LinkedHashMap<>());
-
- private final ConfigurableEnvironment environment;
-
- private final boolean lazilyRegisterPropertySource;
-
- private final Lock propertySourcesLock = new ReentrantLock();
-
-
- DefaultDynamicPropertyRegistry(ConfigurableEnvironment environment, boolean lazilyRegisterPropertySource) {
- this.environment = environment;
- this.lazilyRegisterPropertySource = lazilyRegisterPropertySource;
- }
-
-
- @Override
- public void add(String name, Supplier