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:
@@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <h3>Precedence</h3>
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <h3>Example</h3>
|
||||
*
|
||||
* <p>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}")}.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
* class TestConfig {
|
||||
*
|
||||
* @Bean
|
||||
* ApiServer apiServer() {
|
||||
* return new ApiServer();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* DynamicPropertyRegistrar apiServerProperties(ApiServer apiServer) {
|
||||
* return registry -> registry.add("api.url", apiServer::getUrl);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
* @see DynamicPropertySource
|
||||
* @see DynamicPropertyRegistry
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons()
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DynamicPropertyRegistrar {
|
||||
|
||||
void accept(DynamicPropertyRegistry registry);
|
||||
|
||||
}
|
||||
@@ -27,22 +27,18 @@ import java.util.function.Supplier;
|
||||
* test classes.
|
||||
*
|
||||
* <p>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 {
|
||||
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>Alternatively, dynamic properties can be added to the {@code Environment}
|
||||
* by special beans in the test's {@code ApplicationContext}. See
|
||||
* {@link DynamicPropertyRegistrar} for details.
|
||||
*
|
||||
* <p>This annotation and its supporting infrastructure were originally designed
|
||||
* to allow properties from
|
||||
* <a href="https://www.testcontainers.org/">Testcontainers</a> 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}.
|
||||
*
|
||||
* <p>{@code @DynamicPropertySource}-annotated methods use a
|
||||
* {@code DynamicPropertyRegistry} to add <em>name-value</em> 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.
|
||||
* <p>{@code @DynamicPropertySource} methods use a {@link DynamicPropertyRegistry}
|
||||
* to add <em>name-value</em> 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.
|
||||
*
|
||||
* <p>Methods in integration test classes that are annotated with
|
||||
* {@code @DynamicPropertySource} must be {@code static} and must accept a single
|
||||
* {@link DynamicPropertyRegistry} argument.
|
||||
*
|
||||
* <p>{@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.
|
||||
*
|
||||
* <p>Dynamic properties from methods annotated with {@code @DynamicPropertySource}
|
||||
* will be <em>inherited</em> 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.
|
||||
*
|
||||
* <p><strong>NOTE</strong>: 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.
|
||||
*
|
||||
* <h3>Precedence</h3>
|
||||
*
|
||||
* <p>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;
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Configuration
|
||||
* class TestConfig {
|
||||
*
|
||||
* @Bean
|
||||
* @DynamicPropertySource
|
||||
* ApiServer apiServer(DynamicPropertyRegistry registry) {
|
||||
* ApiServer apiServer = new ApiServer();
|
||||
* registry.add("api.url", apiServer::getUrl);
|
||||
* return apiServer;
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2.5
|
||||
* @see DynamicPropertyRegistry
|
||||
* @see DynamicPropertyRegistrar
|
||||
* @see ContextConfiguration
|
||||
* @see TestPropertySource
|
||||
* @see org.springframework.core.env.PropertySource
|
||||
|
||||
@@ -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<String, Supplier<Object>> 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<Object> valueSupplier) {
|
||||
Assert.hasText(name, "'name' must not be null or blank");
|
||||
Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
|
||||
if (this.lazilyRegisterPropertySource) {
|
||||
ensurePropertySourceIsRegistered();
|
||||
}
|
||||
this.valueSuppliers.put(name, valueSupplier);
|
||||
}
|
||||
|
||||
private void ensurePropertySourceIsRegistered() {
|
||||
MutablePropertySources propertySources = this.environment.getPropertySources();
|
||||
this.propertySourcesLock.lock();
|
||||
try {
|
||||
PropertySource<?> ps = propertySources.get(DynamicValuesPropertySource.PROPERTY_SOURCE_NAME);
|
||||
if (ps == null) {
|
||||
propertySources.addFirst(new DynamicValuesPropertySource(this.valueSuppliers));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
this.propertySourcesLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,35 +26,32 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextCustomizer;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ContextCustomizer} which supports
|
||||
* {@link DynamicPropertySource @DynamicPropertySource} methods and registers a
|
||||
* {@link DynamicPropertyRegistry} as a singleton bean in the container for use
|
||||
* in {@code @Configuration} classes and {@code @Bean} methods.
|
||||
* {@link org.springframework.test.context.DynamicPropertySource @DynamicPropertySource}
|
||||
* methods in test classes and registers a {@link DynamicPropertyRegistrarBeanInitializer}
|
||||
* in the container to eagerly initialize
|
||||
* {@link org.springframework.test.context.DynamicPropertyRegistrar DynamicPropertyRegistrar}
|
||||
* beans.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 5.2.5
|
||||
* @see DynamicPropertiesContextCustomizerFactory
|
||||
* @see DefaultDynamicPropertyRegistry
|
||||
* @see DynamicPropertySourceBeanInitializer
|
||||
* @see DynamicPropertyRegistrarBeanInitializer
|
||||
*/
|
||||
class DynamicPropertiesContextCustomizer implements ContextCustomizer {
|
||||
|
||||
private static final String DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME =
|
||||
DynamicPropertiesContextCustomizer.class.getName() + ".dynamicPropertyRegistry";
|
||||
|
||||
private static final String DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME =
|
||||
DynamicPropertiesContextCustomizer.class.getName() + ".dynamicPropertySourceBeanInitializer";
|
||||
private static final String DYNAMIC_PROPERTY_REGISTRAR_BEAN_INITIALIZER_BEAN_NAME =
|
||||
DynamicPropertiesContextCustomizer.class.getName() + ".dynamicPropertyRegistrarBeanInitializer";
|
||||
|
||||
|
||||
private final Set<Method> methods;
|
||||
@@ -68,29 +65,25 @@ class DynamicPropertiesContextCustomizer implements ContextCustomizer {
|
||||
|
||||
@Override
|
||||
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
|
||||
ConfigurableEnvironment environment = context.getEnvironment();
|
||||
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||
if (!(beanFactory instanceof BeanDefinitionRegistry beanDefinitionRegistry)) {
|
||||
throw new IllegalStateException("BeanFactory must be a BeanDefinitionRegistry");
|
||||
}
|
||||
|
||||
DefaultDynamicPropertyRegistry dynamicPropertyRegistry =
|
||||
new DefaultDynamicPropertyRegistry(environment, this.methods.isEmpty());
|
||||
beanFactory.registerSingleton(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME, dynamicPropertyRegistry);
|
||||
|
||||
if (!beanDefinitionRegistry.containsBeanDefinition(DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME)) {
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(DynamicPropertySourceBeanInitializer.class);
|
||||
if (!beanDefinitionRegistry.containsBeanDefinition(DYNAMIC_PROPERTY_REGISTRAR_BEAN_INITIALIZER_BEAN_NAME)) {
|
||||
BeanDefinition beanDefinition = new RootBeanDefinition(DynamicPropertyRegistrarBeanInitializer.class);
|
||||
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
beanDefinitionRegistry.registerBeanDefinition(
|
||||
DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME, beanDefinition);
|
||||
DYNAMIC_PROPERTY_REGISTRAR_BEAN_INITIALIZER_BEAN_NAME, beanDefinition);
|
||||
}
|
||||
|
||||
if (!this.methods.isEmpty()) {
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
propertySources.addFirst(new DynamicValuesPropertySource(dynamicPropertyRegistry.valueSuppliers));
|
||||
ConfigurableEnvironment environment = context.getEnvironment();
|
||||
DynamicValuesPropertySource propertySource = DynamicValuesPropertySource.getOrCreate(environment);
|
||||
DynamicPropertyRegistry registry = propertySource.dynamicPropertyRegistry;
|
||||
this.methods.forEach(method -> {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry);
|
||||
ReflectionUtils.invokeMethod(method, null, registry);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,14 @@ import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfigurationAttributes;
|
||||
import org.springframework.test.context.ContextCustomizerFactory;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
|
||||
/**
|
||||
* {@link ContextCustomizerFactory} which supports
|
||||
* {@link DynamicPropertySource @DynamicPropertySource} methods and the
|
||||
* registration of a {@link DynamicPropertyRegistry} as a singleton bean in the
|
||||
* container for use in {@code @Configuration} classes and {@code @Bean} methods.
|
||||
* {@link DynamicPropertySource @DynamicPropertySource} methods in test classes
|
||||
* and {@link org.springframework.test.context.DynamicPropertyRegistrar
|
||||
* DynamicPropertyRegistrar} beans in the container.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryInitializer;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.DynamicPropertyRegistrar;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
|
||||
/**
|
||||
* Internal component which eagerly initializes {@link DynamicPropertyRegistrar}
|
||||
* beans.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
*/
|
||||
class DynamicPropertyRegistrarBeanInitializer implements BeanFactoryInitializer<ListableBeanFactory>, EnvironmentAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DynamicPropertyRegistrarBeanInitializer.class);
|
||||
|
||||
|
||||
@Nullable
|
||||
private ConfigurableEnvironment environment;
|
||||
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
if (!(environment instanceof ConfigurableEnvironment configurableEnvironment)) {
|
||||
throw new IllegalArgumentException("Environment must be a ConfigurableEnvironment");
|
||||
}
|
||||
this.environment = configurableEnvironment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(ListableBeanFactory beanFactory) {
|
||||
if (this.environment == null) {
|
||||
throw new IllegalStateException("Environment is required");
|
||||
}
|
||||
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
|
||||
beanFactory, DynamicPropertyRegistrar.class);
|
||||
if (beanNames.length > 0) {
|
||||
DynamicValuesPropertySource propertySource = DynamicValuesPropertySource.getOrCreate(this.environment);
|
||||
DynamicPropertyRegistry registry = propertySource.dynamicPropertyRegistry;
|
||||
for (String name : beanNames) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Eagerly initializing DynamicPropertyRegistrar bean '%s'".formatted(name));
|
||||
}
|
||||
DynamicPropertyRegistrar registrar = beanFactory.getBean(name, DynamicPropertyRegistrar.class);
|
||||
registrar.accept(registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +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 org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryInitializer;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
|
||||
/**
|
||||
* Internal component which eagerly initializes beans created by {@code @Bean}
|
||||
* factory methods annotated with {@link DynamicPropertySource @DynamicPropertySource}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.2
|
||||
*/
|
||||
class DynamicPropertySourceBeanInitializer implements BeanFactoryInitializer<ListableBeanFactory> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DynamicPropertySourceBeanInitializer.class);
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize(ListableBeanFactory beanFactory) {
|
||||
for (String name : beanFactory.getBeanNamesForAnnotation(DynamicPropertySource.class)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Eagerly initializing @DynamicPropertySource bean '%s'".formatted(name));
|
||||
}
|
||||
beanFactory.getBean(name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,11 +17,17 @@
|
||||
package org.springframework.test.context.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.function.SupplierUtils;
|
||||
|
||||
/**
|
||||
@@ -36,9 +42,20 @@ class DynamicValuesPropertySource extends MapPropertySource {
|
||||
|
||||
static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
|
||||
|
||||
final DynamicPropertyRegistry dynamicPropertyRegistry;
|
||||
|
||||
|
||||
DynamicValuesPropertySource() {
|
||||
this(Collections.synchronizedMap(new LinkedHashMap<>()));
|
||||
}
|
||||
|
||||
DynamicValuesPropertySource(Map<String, Supplier<Object>> valueSuppliers) {
|
||||
super(PROPERTY_SOURCE_NAME, Collections.unmodifiableMap(valueSuppliers));
|
||||
this.dynamicPropertyRegistry = (name, valueSupplier) -> {
|
||||
Assert.hasText(name, "'name' must not be null or blank");
|
||||
Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
|
||||
valueSuppliers.put(name, valueSupplier);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,4 +65,27 @@ class DynamicValuesPropertySource extends MapPropertySource {
|
||||
return SupplierUtils.resolve(super.getProperty(name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@code DynamicValuesPropertySource} registered in the environment
|
||||
* or create and register a new {@code DynamicValuesPropertySource} in the
|
||||
* environment.
|
||||
*/
|
||||
static DynamicValuesPropertySource getOrCreate(ConfigurableEnvironment environment) {
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
PropertySource<?> propertySource = propertySources.get(PROPERTY_SOURCE_NAME);
|
||||
if (propertySource instanceof DynamicValuesPropertySource dynamicValuesPropertySource) {
|
||||
return dynamicValuesPropertySource;
|
||||
}
|
||||
else if (propertySource == null) {
|
||||
DynamicValuesPropertySource dynamicValuesPropertySource = new DynamicValuesPropertySource();
|
||||
propertySources.addFirst(dynamicValuesPropertySource);
|
||||
return dynamicValuesPropertySource;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("PropertySource with name '%s' must be a DynamicValuesPropertySource"
|
||||
.formatted(PROPERTY_SOURCE_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user