Skip to content

Commit b47cdaa

Browse files
committed
Lazily register DynamicValuesPropertySource in the TestContext framework
Prior to this commit, as a result of commit 6cdb344, the DynamicValuesPropertySource was eagerly registered in the Environment even if the DynamicPropertyRegistry was never used to register dynamic properties. This commit ensures that the DynamicValuesPropertySource is only registered if we know that the DynamicPropertyRegistry is actually used -- either by a @⁠DynamicPropertySource method in a test class or via a bean in the ApplicationContext that invokes add() on the DynamicPropertyRegistry bean. See gh-32271 Closes gh-32871
1 parent c9a6b7f commit b47cdaa

File tree

5 files changed

+218
-46
lines changed

5 files changed

+218
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.support;
18+
19+
import java.util.Collections;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
import java.util.concurrent.locks.Lock;
23+
import java.util.concurrent.locks.ReentrantLock;
24+
import java.util.function.Supplier;
25+
26+
import org.springframework.core.env.ConfigurableEnvironment;
27+
import org.springframework.core.env.MutablePropertySources;
28+
import org.springframework.core.env.PropertySource;
29+
import org.springframework.test.context.DynamicPropertyRegistry;
30+
import org.springframework.util.Assert;
31+
32+
/**
33+
* Default {@link DynamicPropertyRegistry} implementation.
34+
*
35+
* @author Sam Brannen
36+
* @since 6.2
37+
*/
38+
final class DefaultDynamicPropertyRegistry implements DynamicPropertyRegistry {
39+
40+
final Map<String, Supplier<Object>> valueSuppliers = Collections.synchronizedMap(new LinkedHashMap<>());
41+
42+
private final ConfigurableEnvironment environment;
43+
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;
52+
}
53+
54+
55+
@Override
56+
public void add(String name, Supplier<Object> valueSupplier) {
57+
Assert.hasText(name, "'name' must not be null or blank");
58+
Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
59+
if (this.lazilyRegisterPropertySource) {
60+
ensurePropertySourceIsRegistered();
61+
}
62+
this.valueSuppliers.put(name, valueSupplier);
63+
}
64+
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+
79+
}

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

+21-32
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
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.PropertySource;
29+
import org.springframework.core.env.MutablePropertySources;
3030
import org.springframework.lang.Nullable;
3131
import org.springframework.test.context.ContextCustomizer;
3232
import org.springframework.test.context.DynamicPropertyRegistry;
@@ -75,33 +75,35 @@ private void assertValid(Method method) {
7575

7676
@Override
7777
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
78-
DynamicValuesPropertySource propertySource = getOrAdd(context.getEnvironment());
79-
80-
if (!context.containsBean(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME)) {
81-
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
82-
beanFactory.registerSingleton(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME, propertySource.dynamicPropertyRegistry);
78+
ConfigurableEnvironment environment = context.getEnvironment();
79+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
80+
if (!(beanFactory instanceof BeanDefinitionRegistry beanDefinitionRegistry)) {
81+
throw new IllegalStateException("BeanFactory must be a BeanDefinitionRegistry");
8382
}
8483

85-
if (!context.containsBean(DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME)) {
86-
if (!(context.getBeanFactory() instanceof BeanDefinitionRegistry registry)) {
87-
throw new IllegalStateException("BeanFactory must be a BeanDefinitionRegistry");
88-
}
89-
BeanDefinition beanDefinition = new RootBeanDefinition(DynamicPropertySourceBeanInitializer.class);
90-
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
91-
registry.registerBeanDefinition(DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME, beanDefinition);
84+
DefaultDynamicPropertyRegistry dynamicPropertyRegistry =
85+
new DefaultDynamicPropertyRegistry(environment, this.methods.isEmpty());
86+
beanFactory.registerSingleton(DYNAMIC_PROPERTY_REGISTRY_BEAN_NAME, dynamicPropertyRegistry);
87+
88+
BeanDefinition beanDefinition = new RootBeanDefinition(DynamicPropertySourceBeanInitializer.class);
89+
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
90+
beanDefinitionRegistry.registerBeanDefinition(
91+
DYNAMIC_PROPERTY_SOURCE_BEAN_INITIALIZER_BEAN_NAME, beanDefinition);
92+
93+
if (!this.methods.isEmpty()) {
94+
MutablePropertySources propertySources = environment.getPropertySources();
95+
propertySources.addFirst(new DynamicValuesPropertySource(dynamicPropertyRegistry.valueSuppliers));
96+
this.methods.forEach(method -> {
97+
ReflectionUtils.makeAccessible(method);
98+
ReflectionUtils.invokeMethod(method, null, dynamicPropertyRegistry);
99+
});
92100
}
93-
94-
this.methods.forEach(method -> {
95-
ReflectionUtils.makeAccessible(method);
96-
ReflectionUtils.invokeMethod(method, null, propertySource.dynamicPropertyRegistry);
97-
});
98101
}
99102

100103
Set<Method> getMethods() {
101104
return this.methods;
102105
}
103106

104-
105107
@Override
106108
public boolean equals(@Nullable Object other) {
107109
return (this == other || (other instanceof DynamicPropertiesContextCustomizer that &&
@@ -113,17 +115,4 @@ public int hashCode() {
113115
return this.methods.hashCode();
114116
}
115117

116-
117-
private static DynamicValuesPropertySource getOrAdd(ConfigurableEnvironment environment) {
118-
PropertySource<?> propertySource = environment.getPropertySources()
119-
.get(DynamicValuesPropertySource.PROPERTY_SOURCE_NAME);
120-
if (propertySource == null) {
121-
environment.getPropertySources().addFirst(new DynamicValuesPropertySource());
122-
return getOrAdd(environment);
123-
}
124-
Assert.state(propertySource instanceof DynamicValuesPropertySource,
125-
"Incorrect DynamicValuesPropertySource type registered");
126-
return (DynamicValuesPropertySource) propertySource;
127-
}
128-
129118
}

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

-14
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,11 @@
1717
package org.springframework.test.context.support;
1818

1919
import java.util.Collections;
20-
import java.util.LinkedHashMap;
2120
import java.util.Map;
2221
import java.util.function.Supplier;
2322

2423
import org.springframework.core.env.MapPropertySource;
2524
import org.springframework.lang.Nullable;
26-
import org.springframework.test.context.DynamicPropertyRegistry;
27-
import org.springframework.util.Assert;
2825
import org.springframework.util.function.SupplierUtils;
2926

3027
/**
@@ -39,20 +36,9 @@ class DynamicValuesPropertySource extends MapPropertySource {
3936

4037
static final String PROPERTY_SOURCE_NAME = "Dynamic Test Properties";
4138

42-
final DynamicPropertyRegistry dynamicPropertyRegistry;
43-
44-
45-
DynamicValuesPropertySource() {
46-
this(Collections.synchronizedMap(new LinkedHashMap<>()));
47-
}
4839

4940
DynamicValuesPropertySource(Map<String, Supplier<Object>> valueSuppliers) {
5041
super(PROPERTY_SOURCE_NAME, Collections.unmodifiableMap(valueSuppliers));
51-
this.dynamicPropertyRegistry = (name, valueSupplier) -> {
52-
Assert.hasText(name, "'name' must not be null or blank");
53-
Assert.notNull(valueSupplier, "'valueSupplier' must not be null");
54-
valueSuppliers.put(name, valueSupplier);
55-
};
5642
}
5743

5844
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
import org.junit.platform.suite.api.SelectClasses;
20+
import org.junit.platform.suite.api.Suite;
21+
22+
/**
23+
* JUnit Platform based test suite for tests that involve the Spring TestContext
24+
* Framework and dynamic properties.
25+
*
26+
* <p><strong>This suite is only intended to be used manually within an IDE.</strong>
27+
*
28+
* <h3>Logging Configuration</h3>
29+
*
30+
* <p>In order for our log4j2 configuration to be used in an IDE, you must
31+
* set the following system property before running any tests &mdash; for
32+
* example, in <em>Run Configurations</em> in Eclipse.
33+
*
34+
* <pre style="code">
35+
* -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager
36+
* </pre>
37+
*
38+
* @author Sam Brannen
39+
* @since 6.2
40+
*/
41+
@Suite
42+
@SelectClasses(
43+
value = {
44+
DynamicPropertyRegistryIntegrationTests.class,
45+
DynamicPropertySourceIntegrationTests.class
46+
},
47+
names = {
48+
"org.springframework.test.context.junit.jupiter.nested.DynamicPropertySourceNestedTests",
49+
"org.springframework.test.context.support.DefaultTestPropertySourcesIntegrationTests",
50+
"org.springframework.test.context.support.DynamicPropertiesContextCustomizerFactoryTests",
51+
"org.springframework.test.context.support.DynamicValuesPropertySourceTests"
52+
}
53+
)
54+
class DynamicPropertiesTestSuite {
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.support;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.core.env.ConfigurableEnvironment;
24+
import org.springframework.core.env.MutablePropertySources;
25+
import org.springframework.test.annotation.DirtiesContext;
26+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
/**
31+
* Integration tests which ensure that test-related property sources are not
32+
* registered by default.
33+
*
34+
* @author Sam Brannen
35+
* @since 6.2
36+
*/
37+
@SpringJUnitConfig
38+
@DirtiesContext
39+
class DefaultTestPropertySourcesIntegrationTests {
40+
41+
@Autowired
42+
ConfigurableEnvironment env;
43+
44+
45+
@Test
46+
void ensureTestRelatedPropertySourcesAreNotRegisteredByDefault() {
47+
assertPropertySourceIsNotRegistered(TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
48+
assertPropertySourceIsNotRegistered(DynamicValuesPropertySource.PROPERTY_SOURCE_NAME);
49+
}
50+
51+
private void assertPropertySourceIsNotRegistered(String name) {
52+
MutablePropertySources propertySources = this.env.getPropertySources();
53+
assertThat(propertySources.contains(name))
54+
.as("PropertySource \"%s\" should not be registered by default", name)
55+
.isFalse();
56+
}
57+
58+
59+
@Configuration
60+
static class Config {
61+
}
62+
63+
}

0 commit comments

Comments
 (0)