Skip to content

Commit 0f28d50

Browse files
committed
Revise DynamicPropertyRegistrar support
With this commit, a DynamicValuesPropertySource is now created and registered with the Environment on demand whenever a DefaultDynamicPropertyRegistry is created, which only occurs once in DynamicPropertiesContextCustomizer and once in DynamicPropertyRegistrarBeanInitializer. In addition, DefaultDynamicPropertyRegistry now operates on the valueSuppliers map stored in the singleton DynamicValuesPropertySource registered with the Environment, which allows @⁠DynamicPropertySource methods and DynamicPropertyRegistrar beans to transparently populate the same DynamicValuesPropertySource. See spring-projectsgh-33501
1 parent d3f28f1 commit 0f28d50

File tree

6 files changed

+88
-67
lines changed

6 files changed

+88
-67
lines changed

spring-test/src/main/java/org/springframework/test/context/support/DefaultDynamicPropertyRegistry.java

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,10 @@
1616

1717
package org.springframework.test.context.support;
1818

19-
import java.util.Collections;
20-
import java.util.LinkedHashMap;
2119
import java.util.Map;
22-
import java.util.concurrent.locks.Lock;
23-
import java.util.concurrent.locks.ReentrantLock;
2420
import java.util.function.Supplier;
2521

2622
import org.springframework.core.env.ConfigurableEnvironment;
27-
import org.springframework.core.env.MutablePropertySources;
28-
import org.springframework.core.env.PropertySource;
2923
import org.springframework.test.context.DynamicPropertyRegistry;
3024
import org.springframework.util.Assert;
3125

@@ -37,43 +31,21 @@
3731
*/
3832
final class DefaultDynamicPropertyRegistry implements DynamicPropertyRegistry {
3933

40-
final Map<String, Supplier<Object>> valueSuppliers = Collections.synchronizedMap(new LinkedHashMap<>());
34+
private final Map<String, Supplier<Object>> valueSuppliers;
4135

42-
private final ConfigurableEnvironment environment;
4336

44-
private final boolean lazilyRegisterPropertySource;
45-
46-
private final Lock propertySourcesLock = new ReentrantLock();
47-
48-
49-
DefaultDynamicPropertyRegistry(ConfigurableEnvironment environment, boolean lazilyRegisterPropertySource) {
50-
this.environment = environment;
51-
this.lazilyRegisterPropertySource = lazilyRegisterPropertySource;
37+
DefaultDynamicPropertyRegistry(ConfigurableEnvironment environment) {
38+
DynamicValuesPropertySource dynamicValuesPropertySource =
39+
DynamicValuesPropertySource.getOrCreate(environment);
40+
this.valueSuppliers = dynamicValuesPropertySource.valueSuppliers;
5241
}
5342

5443

5544
@Override
5645
public void add(String name, Supplier<Object> valueSupplier) {
5746
Assert.hasText(name, "'name' must not be null or blank");
5847
Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
59-
if (this.lazilyRegisterPropertySource) {
60-
ensurePropertySourceIsRegistered();
61-
}
6248
this.valueSuppliers.put(name, valueSupplier);
6349
}
6450

65-
private void ensurePropertySourceIsRegistered() {
66-
MutablePropertySources propertySources = this.environment.getPropertySources();
67-
this.propertySourcesLock.lock();
68-
try {
69-
PropertySource<?> ps = propertySources.get(DynamicValuesPropertySource.PROPERTY_SOURCE_NAME);
70-
if (ps == null) {
71-
propertySources.addFirst(new DynamicValuesPropertySource(this.valueSuppliers));
72-
}
73-
}
74-
finally {
75-
this.propertySourcesLock.unlock();
76-
}
77-
}
78-
7951
}

spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertiesContextCustomizer.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@
2626
import org.springframework.beans.factory.support.RootBeanDefinition;
2727
import org.springframework.context.ConfigurableApplicationContext;
2828
import org.springframework.core.env.ConfigurableEnvironment;
29-
import org.springframework.core.env.MutablePropertySources;
3029
import org.springframework.lang.Nullable;
3130
import org.springframework.test.context.ContextCustomizer;
3231
import org.springframework.test.context.DynamicPropertyRegistry;
33-
import org.springframework.test.context.DynamicPropertySource;
3432
import org.springframework.test.context.MergedContextConfiguration;
3533
import org.springframework.util.Assert;
3634
import org.springframework.util.ReflectionUtils;
3735

3836
/**
3937
* {@link ContextCustomizer} which supports
40-
* {@link DynamicPropertySource @DynamicPropertySource} methods and registers a
41-
* {@link DynamicPropertyRegistry} as a singleton bean in the container for use
42-
* in {@code @Configuration} classes and {@code @Bean} methods.
38+
* {@link org.springframework.test.context.DynamicPropertySource @DynamicPropertySource}
39+
* methods and registers a {@link DynamicPropertyRegistrarBeanInitializer} in the
40+
* container to eagerly initialize
41+
* {@link org.springframework.test.context.DynamicPropertyRegistrar DynamicPropertyRegistrar}
42+
* beans.
4343
*
4444
* @author Phillip Webb
4545
* @author Sam Brannen
@@ -79,10 +79,7 @@ public void customizeContext(ConfigurableApplicationContext context, MergedConte
7979

8080
if (!this.methods.isEmpty()) {
8181
ConfigurableEnvironment environment = context.getEnvironment();
82-
DefaultDynamicPropertyRegistry dynamicPropertyRegistry =
83-
new DefaultDynamicPropertyRegistry(environment, false);
84-
MutablePropertySources propertySources = environment.getPropertySources();
85-
propertySources.addFirst(new DynamicValuesPropertySource(dynamicPropertyRegistry.valueSuppliers));
82+
DefaultDynamicPropertyRegistry dynamicPropertyRegistry = new DefaultDynamicPropertyRegistry(environment);
8683
this.methods.forEach(method -> {
8784
ReflectionUtils.makeAccessible(method);
8885
ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry);

spring-test/src/main/java/org/springframework/test/context/support/DynamicPropertyRegistrarBeanInitializer.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ public void initialize(ListableBeanFactory beanFactory) {
6060
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
6161
beanFactory, DynamicPropertyRegistrar.class);
6262
if (beanNames.length > 0) {
63-
DefaultDynamicPropertyRegistry dynamicPropertyRegistry =
64-
new DefaultDynamicPropertyRegistry(this.environment, true);
65-
63+
DefaultDynamicPropertyRegistry dynamicPropertyRegistry = new DefaultDynamicPropertyRegistry(this.environment);
6664
for (String name : beanNames) {
6765
if (logger.isDebugEnabled()) {
6866
logger.debug("Eagerly initializing DynamicPropertyRegistrar bean '%s'".formatted(name));

spring-test/src/main/java/org/springframework/test/context/support/DynamicValuesPropertySource.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,16 @@
1717
package org.springframework.test.context.support;
1818

1919
import java.util.Collections;
20+
import java.util.LinkedHashMap;
2021
import java.util.Map;
22+
import java.util.concurrent.locks.Lock;
23+
import java.util.concurrent.locks.ReentrantLock;
2124
import java.util.function.Supplier;
2225

26+
import org.springframework.core.env.ConfigurableEnvironment;
2327
import org.springframework.core.env.MapPropertySource;
28+
import org.springframework.core.env.MutablePropertySources;
29+
import org.springframework.core.env.PropertySource;
2430
import org.springframework.lang.Nullable;
2531
import org.springframework.util.function.SupplierUtils;
2632

@@ -36,9 +42,18 @@ class DynamicValuesPropertySource extends MapPropertySource {
3642

3743
static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
3844

45+
private static final Lock propertySourcesLock = new ReentrantLock();
46+
47+
final Map<String, Supplier<Object>> valueSuppliers;
48+
49+
50+
DynamicValuesPropertySource() {
51+
this(Collections.synchronizedMap(new LinkedHashMap<>()));
52+
}
3953

4054
DynamicValuesPropertySource(Map<String, Supplier<Object>> valueSuppliers) {
4155
super(PROPERTY_SOURCE_NAME, Collections.unmodifiableMap(valueSuppliers));
56+
this.valueSuppliers = valueSuppliers;
4257
}
4358

4459

@@ -48,4 +63,29 @@ public Object getProperty(String name) {
4863
return SupplierUtils.resolve(super.getProperty(name));
4964
}
5065

66+
67+
static DynamicValuesPropertySource getOrCreate(ConfigurableEnvironment environment) {
68+
propertySourcesLock.lock();
69+
try {
70+
MutablePropertySources propertySources = environment.getPropertySources();
71+
PropertySource<?> ps = propertySources.get(PROPERTY_SOURCE_NAME);
72+
if (ps instanceof DynamicValuesPropertySource dynamicValuesPropertySource) {
73+
return dynamicValuesPropertySource;
74+
}
75+
else if (ps == null) {
76+
DynamicValuesPropertySource dynamicValuesPropertySource = new DynamicValuesPropertySource();
77+
propertySources.addFirst(dynamicValuesPropertySource);
78+
return dynamicValuesPropertySource;
79+
}
80+
else {
81+
throw new IllegalStateException(
82+
"PropertySource with name '%s' must be a DynamicValuesPropertySource"
83+
.formatted(PROPERTY_SOURCE_NAME));
84+
}
85+
}
86+
finally {
87+
propertySourcesLock.unlock();
88+
}
89+
}
90+
5191
}

spring-test/src/test/java/org/springframework/test/context/DynamicPropertyRegistrarIntegrationTests.java

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939
* @see DynamicPropertySourceIntegrationTests
4040
*/
4141
@SpringJUnitConfig
42-
@TestPropertySource(properties = "api.url: https://example.com/test")
42+
@TestPropertySource(properties = "api.url.1: https://example.com/test")
4343
class DynamicPropertyRegistrarIntegrationTests {
4444

45-
private static final String API_URL = "api.url";
45+
private static final String API_URL_1 = "api.url.1";
46+
private static final String API_URL_2 = "api.url.2";
47+
4648

4749
@Test
4850
void customDynamicPropertyRegistryCanExistInApplicationContext(
@@ -55,43 +57,49 @@ void customDynamicPropertyRegistryCanExistInApplicationContext(
5557

5658
@Test
5759
void dynamicPropertySourceOverridesTestPropertySource(@Autowired ConfigurableEnvironment env) {
58-
assertApiUrlIsDynamic(env.getProperty(API_URL));
60+
assertApiUrlIsDynamic1(env.getProperty(API_URL_1));
5961

6062
MutablePropertySources propertySources = env.getPropertySources();
6163
assertThat(propertySources.size()).isGreaterThanOrEqualTo(4);
6264
assertThat(propertySources.contains("Inlined Test Properties")).isTrue();
6365
assertThat(propertySources.contains("Dynamic Test Properties")).isTrue();
64-
assertThat(propertySources.get("Inlined Test Properties").getProperty(API_URL)).isEqualTo("https://example.com/test");
65-
assertThat(propertySources.get("Dynamic Test Properties").getProperty(API_URL)).isEqualTo("https://example.com/dynamic");
66+
assertThat(propertySources.get("Inlined Test Properties").getProperty(API_URL_1)).isEqualTo("https://example.com/test");
67+
assertThat(propertySources.get("Dynamic Test Properties").getProperty(API_URL_1)).isEqualTo("https://example.com/dynamic/1");
68+
assertThat(propertySources.get("Dynamic Test Properties").getProperty(API_URL_2)).isEqualTo("https://example.com/dynamic/2");
6669
}
6770

6871
@Test
69-
void testReceivesDynamicProperty(@Value("${api.url}") String apiUrl) {
70-
assertApiUrlIsDynamic(apiUrl);
72+
void testReceivesDynamicProperties(@Value("${api.url.1}") String apiUrl1, @Value("${api.url.2}") String apiUrl2) {
73+
assertApiUrlIsDynamic1(apiUrl1);
74+
assertApiUrlIsDynamic2(apiUrl2);
7175
}
7276

7377
@Test
7478
void environmentInjectedServiceCanRetrieveDynamicProperty(@Autowired EnvironmentInjectedService service) {
75-
assertApiUrlIsDynamic(service);
79+
assertApiUrlIsDynamic1(service);
7680
}
7781

7882
@Test
7983
void constructorInjectedServiceReceivesDynamicProperty(@Autowired ConstructorInjectedService service) {
80-
assertApiUrlIsDynamic(service);
84+
assertApiUrlIsDynamic1(service);
8185
}
8286

8387
@Test
8488
void setterInjectedServiceReceivesDynamicProperty(@Autowired SetterInjectedService service) {
85-
assertApiUrlIsDynamic(service);
89+
assertApiUrlIsDynamic1(service);
8690
}
8791

8892

89-
private static void assertApiUrlIsDynamic(ApiUrlClient service) {
90-
assertApiUrlIsDynamic(service.getApiUrl());
93+
private static void assertApiUrlIsDynamic1(ApiUrlClient service) {
94+
assertApiUrlIsDynamic1(service.getApiUrl());
95+
}
96+
97+
private static void assertApiUrlIsDynamic1(String apiUrl) {
98+
assertThat(apiUrl).isEqualTo("https://example.com/dynamic/1");
9199
}
92100

93-
private static void assertApiUrlIsDynamic(String apiUrl) {
94-
assertThat(apiUrl).isEqualTo("https://example.com/dynamic");
101+
private static void assertApiUrlIsDynamic2(String apiUrl) {
102+
assertThat(apiUrl).isEqualTo("https://example.com/dynamic/2");
95103
}
96104

97105

@@ -109,8 +117,13 @@ ApiServer apiServer() {
109117
// context which further ensures that the dynamic "api.url" property is
110118
// available to all standard singleton beans.
111119
@Bean
112-
DynamicPropertyRegistrar apiServerProperties(ApiServer apiServer) {
113-
return registry -> registry.add(API_URL, apiServer::getUrl);
120+
DynamicPropertyRegistrar apiServerProperties1(ApiServer apiServer) {
121+
return registry -> registry.add(API_URL_1, () -> apiServer.getUrl() + "/1");
122+
}
123+
124+
@Bean
125+
DynamicPropertyRegistrar apiServerProperties2(ApiServer apiServer) {
126+
return registry -> registry.add(API_URL_2, () -> apiServer.getUrl() + "/2");
114127
}
115128

116129
@Bean
@@ -138,7 +151,7 @@ static class EnvironmentInjectedService implements ApiUrlClient {
138151

139152
@Override
140153
public String getApiUrl() {
141-
return this.env.getProperty(API_URL);
154+
return this.env.getProperty(API_URL_1);
142155
}
143156
}
144157

@@ -147,7 +160,7 @@ static class ConstructorInjectedService implements ApiUrlClient {
147160
private final String apiUrl;
148161

149162

150-
ConstructorInjectedService(@Value("${api.url}") String apiUrl) {
163+
ConstructorInjectedService(@Value("${api.url.1}") String apiUrl) {
151164
this.apiUrl = apiUrl;
152165
}
153166

@@ -163,7 +176,7 @@ static class SetterInjectedService implements ApiUrlClient {
163176

164177

165178
@Autowired
166-
void setApiUrl(@Value("${api.url}") String apiUrl) {
179+
void setApiUrl(@Value("${api.url.1}") String apiUrl) {
167180
this.apiUrl = apiUrl;
168181
}
169182

spring-test/src/test/java/org/springframework/test/context/DynamicPropertySourceIntegrationTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
class DynamicPropertySourceIntegrationTests {
5050

5151
private static final String TEST_CONTAINER_IP = "test.container.ip";
52+
private static final String MAGIC_WORD = "magic.word";
5253

5354
static {
5455
System.setProperty(TEST_CONTAINER_IP, "system");
@@ -91,11 +92,11 @@ void dynamicPropertySourceOverridesTestPropertySourceAndSystemProperty(@Autowire
9192
assertThat(propertySources.contains("Inlined Test Properties")).isTrue();
9293
assertThat(propertySources.contains("systemProperties")).isTrue();
9394
assertThat(propertySources.get("Dynamic Test Properties").getProperty(TEST_CONTAINER_IP)).isEqualTo("127.0.0.1");
94-
// assertThat(propertySources.get("Dynamic Test Properties").getProperty("magic.word")).isEqualTo("enigma");
95+
assertThat(propertySources.get("Dynamic Test Properties").getProperty(MAGIC_WORD)).isEqualTo("enigma");
9596
assertThat(propertySources.get("Inlined Test Properties").getProperty(TEST_CONTAINER_IP)).isEqualTo("test");
9697
assertThat(propertySources.get("systemProperties").getProperty(TEST_CONTAINER_IP)).isEqualTo("system");
9798
assertThat(env.getProperty(TEST_CONTAINER_IP)).isEqualTo("127.0.0.1");
98-
// assertThat(env.getProperty("magic.word")).isEqualTo("enigma");
99+
assertThat(env.getProperty(MAGIC_WORD)).isEqualTo("enigma");
99100
}
100101

101102
@Test
@@ -112,7 +113,7 @@ static class Config {
112113

113114
@Bean
114115
DynamicPropertyRegistrar magicWordProperties() {
115-
return registry -> registry.add("magic.word", () -> "engima");
116+
return registry -> registry.add(MAGIC_WORD, () -> "enigma");
116117
}
117118

118119
@Bean

0 commit comments

Comments
 (0)