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:
Sam Brannen
2024-09-10 16:34:50 +02:00
parent 78028cde05
commit e7b52cf8b2
15 changed files with 429 additions and 301 deletions

View File

@@ -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 &mdash; for example,
* via {@code @Value("${api.url}")}.
*
* <pre class="code">
* &#064;Configuration
* class TestConfig {
*
* &#064;Bean
* ApiServer apiServer() {
* return new ApiServer();
* }
*
* &#064;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);
}

View File

@@ -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 {

View File

@@ -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">
* &#064;Configuration
* class TestConfig {
*
* &#064;Bean
* &#064;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

View File

@@ -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();
}
}
}

View File

@@ -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);
});
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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));
}
}
}