Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.

Commit 9fcfd7e

Browse files
committed
Introduce ConfigurableEnvironment#merge
Prior to this change, AbstractApplicationContext#setParent replaced the child context's Environment with the parent's Environment if available. This has the negative effect of potentially changing the type of the child context's Environment, and in any case causes property sources added directly against the child environment to be ignored. This situation could easily occur if a WebApplicationContext child had a non-web ApplicationContext set as its parent. In this case the parent Environment type would (likely) be StandardEnvironment, while the child Environment type would (likely) be StandardServletEnvironment. By directly inheriting the parent environment, critical property sources such as ServletContextPropertySource are lost entirely. This commit introduces the concept of merging an environment through the new ConfigurableEnvironment#merge method. Instead of replacing the child's environment with the parent's, AbstractApplicationContext#setParent now merges property sources as well as active and default profile names from the parent into the child. In this way, distinct environment objects are maintained with specific types and property sources preserved. See #merge Javadoc for additional details. Issue: SPR-9444, SPR-9439
1 parent 5874383 commit 9fcfd7e

File tree

5 files changed

+92
-7
lines changed

5 files changed

+92
-7
lines changed

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -378,14 +378,19 @@ protected ResourcePatternResolver getResourcePatternResolver() {
378378

379379
/**
380380
* {@inheritDoc}
381-
* <p>The parent {@linkplain #getEnvironment() environment} is
382-
* delegated to this (child) context if the parent is a
383-
* {@link ConfigurableApplicationContext} implementation.
381+
* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
382+
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
383+
* this (child) application context environment if the parent is non-{@code null} and
384+
* its environment is an instance of {@link ConfigurableEnvironment}.
385+
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
384386
*/
385387
public void setParent(ApplicationContext parent) {
386388
this.parent = parent;
387-
if (parent instanceof ConfigurableApplicationContext) {
388-
this.setEnvironment(((ConfigurableApplicationContext)parent).getEnvironment());
389+
if (parent != null) {
390+
Object parentEnvironment = parent.getEnvironment();
391+
if (parentEnvironment instanceof ConfigurableEnvironment) {
392+
this.environment.merge((ConfigurableEnvironment)parentEnvironment);
393+
}
389394
}
390395
}
391396

spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java

+14
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,20 @@ protected String getSystemAttribute(String propertyName) {
387387
return systemProperties;
388388
}
389389

390+
public void merge(ConfigurableEnvironment parent) {
391+
for (PropertySource<?> ps : parent.getPropertySources()) {
392+
if (!this.propertySources.contains(ps.getName())) {
393+
this.propertySources.addLast(ps);
394+
}
395+
}
396+
for (String profile : parent.getActiveProfiles()) {
397+
this.activeProfiles.add(profile);
398+
}
399+
for (String profile : parent.getDefaultProfiles()) {
400+
this.defaultProfiles.add(profile);
401+
}
402+
}
403+
390404

391405
//---------------------------------------------------------------------
392406
// Implementation of ConfigurablePropertyResolver interface

spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java

+20
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,24 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
149149
*/
150150
Map<String, Object> getSystemProperties();
151151

152+
/**
153+
* Append the given parent environment's active profiles, default profiles and
154+
* property sources to this (child) environment's respective collections of each.
155+
* <p>For any identically-named {@code PropertySource} instance existing in both
156+
* parent and child, the child instance is to be preserved and the parent instance
157+
* discarded. This has the effect of allowing overriding of property sources by the
158+
* child as well as avoiding redundant searches through common property source types,
159+
* e.g. system environment and system properties.
160+
* <p>Active and default profile names are also filtered for duplicates, to avoid
161+
* confusion and redundant storage.
162+
* <p>The parent environment remains unmodified in any case. Note that any changes to
163+
* the parent environment occurring after the call to {@code merge} will not be
164+
* reflected in the child. Therefore, care should be taken to configure parent
165+
* property sources and profile information prior to calling {@code merge}.
166+
* @param parent the environment to merge with
167+
* @since 3.2
168+
* @see org.springframework.context.support.AbstractApplicationContext#setParent
169+
*/
170+
void merge(ConfigurableEnvironment parent);
171+
152172
}

spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java

+41
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,47 @@ public class StandardEnvironmentTests {
5656

5757
private ConfigurableEnvironment environment = new StandardEnvironment();
5858

59+
@Test
60+
public void merge() {
61+
ConfigurableEnvironment child = new StandardEnvironment();
62+
child.setActiveProfiles("c1", "c2");
63+
child.getPropertySources().addLast(
64+
new MockPropertySource("childMock")
65+
.withProperty("childKey", "childVal")
66+
.withProperty("bothKey", "childBothVal"));
67+
68+
ConfigurableEnvironment parent = new StandardEnvironment();
69+
parent.setActiveProfiles("p1", "p2");
70+
parent.getPropertySources().addLast(
71+
new MockPropertySource("parentMock")
72+
.withProperty("parentKey", "parentVal")
73+
.withProperty("bothKey", "parentBothVal"));
74+
75+
assertThat(child.getProperty("childKey"), is("childVal"));
76+
assertThat(child.getProperty("parentKey"), nullValue());
77+
assertThat(child.getProperty("bothKey"), is("childBothVal"));
78+
79+
assertThat(parent.getProperty("childKey"), nullValue());
80+
assertThat(parent.getProperty("parentKey"), is("parentVal"));
81+
assertThat(parent.getProperty("bothKey"), is("parentBothVal"));
82+
83+
assertThat(child.getActiveProfiles(), equalTo(new String[]{"c1","c2"}));
84+
assertThat(parent.getActiveProfiles(), equalTo(new String[]{"p1","p2"}));
85+
86+
child.merge(parent);
87+
88+
assertThat(child.getProperty("childKey"), is("childVal"));
89+
assertThat(child.getProperty("parentKey"), is("parentVal"));
90+
assertThat(child.getProperty("bothKey"), is("childBothVal"));
91+
92+
assertThat(parent.getProperty("childKey"), nullValue());
93+
assertThat(parent.getProperty("parentKey"), is("parentVal"));
94+
assertThat(parent.getProperty("bothKey"), is("parentBothVal"));
95+
96+
assertThat(child.getActiveProfiles(), equalTo(new String[]{"c1","c2","p1","p2"}));
97+
assertThat(parent.getActiveProfiles(), equalTo(new String[]{"p1","p2"}));
98+
}
99+
59100
@Test
60101
public void propertySourceOrder() {
61102
ConfigurableEnvironment env = new StandardEnvironment();

spring-webmvc/src/test/java/org/springframework/web/context/XmlWebApplicationContextTests.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class XmlWebApplicationContextTests extends AbstractApplicationContextTes
4848
protected ConfigurableApplicationContext createContext() throws Exception {
4949
InitAndIB.constructed = false;
5050
root = new XmlWebApplicationContext();
51+
root.getEnvironment().addActiveProfile("rootProfile1");
5152
MockServletContext sc = new MockServletContext("");
5253
root.setServletContext(sc);
5354
root.setConfigLocations(new String[] {"/org/springframework/web/context/WEB-INF/applicationContext.xml"});
@@ -69,6 +70,7 @@ public Object postProcessAfterInitialization(Object bean, String name) throws Be
6970
});
7071
root.refresh();
7172
XmlWebApplicationContext wac = new XmlWebApplicationContext();
73+
wac.getEnvironment().addActiveProfile("wacProfile1");
7274
wac.setParent(root);
7375
wac.setServletContext(sc);
7476
wac.setNamespace("test-servlet");
@@ -77,8 +79,11 @@ public Object postProcessAfterInitialization(Object bean, String name) throws Be
7779
return wac;
7880
}
7981

80-
public void testEnvironmentInheritance() {
81-
assertThat(this.applicationContext.getEnvironment(), sameInstance(this.root.getEnvironment()));
82+
public void testEnvironmentMerge() {
83+
assertThat(this.root.getEnvironment().acceptsProfiles("rootProfile1"), is(true));
84+
assertThat(this.root.getEnvironment().acceptsProfiles("wacProfile1"), is(false));
85+
assertThat(this.applicationContext.getEnvironment().acceptsProfiles("rootProfile1"), is(true));
86+
assertThat(this.applicationContext.getEnvironment().acceptsProfiles("wacProfile1"), is(true));
8287
}
8388

8489
/**

0 commit comments

Comments
 (0)