Skip to content

Commit ba28122

Browse files
committed
Apply bootstrap initializers to all contexts in a hierarchy
The fix for spring-projectsgh-153 was sensible, but has now exposed another issue which is that the bootstrap initializers, whilst they are now only created once, actually only get applied to the parent context in a hierarchy. Most of the time this doesn't matter because the child contexts all benefit from environment changes and additional beans added to the parent. If the children are supposed to actually have different environments, though, there is a problem. So this change locates the existing bootstrap context and applies it to each new context created separately. Unfortunately we have to use reflection to do that for now because there is no way to discover the parent with a public API before the context is refreshed. See spring-projectsgh-190
1 parent 70d601d commit ba28122

File tree

3 files changed

+99
-9
lines changed

3 files changed

+99
-9
lines changed

spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java

+39-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.bootstrap;
1818

19+
import java.lang.reflect.Field;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -46,6 +47,7 @@
4647
import org.springframework.core.env.SystemEnvironmentPropertySource;
4748
import org.springframework.core.io.support.SpringFactoriesLoader;
4849
import org.springframework.util.ClassUtils;
50+
import org.springframework.util.ReflectionUtils;
4951
import org.springframework.util.StringUtils;
5052

5153
/**
@@ -81,26 +83,56 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
8183
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
8284
return;
8385
}
84-
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
86+
ConfigurableApplicationContext context = null;
87+
String configName = environment
88+
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
89+
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
90+
.getInitializers()) {
8591
if (initializer instanceof ParentContextApplicationContextInitializer) {
86-
return;
92+
context = findBootstrapContext(
93+
(ParentContextApplicationContextInitializer) initializer,
94+
configName);
8795
}
8896
}
89-
ConfigurableApplicationContext context = bootstrapServiceContext(environment,
90-
event.getSpringApplication());
97+
if (context == null) {
98+
context = bootstrapServiceContext(environment, event.getSpringApplication(),
99+
configName);
100+
}
91101
apply(context, event.getSpringApplication(), environment);
92102
}
93103

104+
private ConfigurableApplicationContext findBootstrapContext(
105+
ParentContextApplicationContextInitializer initializer, String configName) {
106+
Field field = ReflectionUtils
107+
.findField(ParentContextApplicationContextInitializer.class, "parent");
108+
ReflectionUtils.makeAccessible(field);
109+
ConfigurableApplicationContext parent = safeCast(
110+
ConfigurableApplicationContext.class,
111+
ReflectionUtils.getField(field, initializer));
112+
if (parent != null && !configName.equals(parent.getId())) {
113+
parent = safeCast(ConfigurableApplicationContext.class, parent.getParent());
114+
}
115+
return parent;
116+
}
117+
118+
private <T> T safeCast(Class<T> type, Object object) {
119+
try {
120+
return type.cast(object);
121+
}
122+
catch (ClassCastException e) {
123+
return null;
124+
}
125+
}
126+
94127
private ConfigurableApplicationContext bootstrapServiceContext(
95-
ConfigurableEnvironment environment, final SpringApplication application) {
128+
ConfigurableEnvironment environment, final SpringApplication application,
129+
String configName) {
96130
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
97131
MutablePropertySources bootstrapProperties = bootstrapEnvironment
98132
.getPropertySources();
99133
for (PropertySource<?> source : bootstrapProperties) {
100134
bootstrapProperties.remove(source.getName());
101135
}
102-
String configName = environment
103-
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
104136
String configLocation = environment
105137
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
106138
Map<String, Object> bootstrapMap = new HashMap<>();
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
11
package org.springframework.cloud.bootstrap;
22

3+
import java.util.Collections;
4+
5+
import org.springframework.context.ApplicationContextInitializer;
6+
import org.springframework.context.ConfigurableApplicationContext;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
39
import org.springframework.core.annotation.Order;
10+
import org.springframework.core.env.ConfigurableEnvironment;
11+
import org.springframework.core.env.MapPropertySource;
412

513
import static org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration.firstToBeCreated;
614

715
/**
816
* @author Spencer Gibb
917
*/
1018
@Order(0)
19+
@Configuration
1120
public class TestBootstrapConfiguration {
1221

1322
public TestBootstrapConfiguration() {
1423
firstToBeCreated.compareAndSet(null, TestBootstrapConfiguration.class);
1524
}
1625

26+
@Bean
27+
public ApplicationContextInitializer<ConfigurableApplicationContext> customInitializer() {
28+
return new ApplicationContextInitializer<ConfigurableApplicationContext>() {
29+
30+
@Override
31+
public void initialize(ConfigurableApplicationContext applicationContext) {
32+
ConfigurableEnvironment environment = applicationContext.getEnvironment();
33+
environment.getPropertySources()
34+
.addLast(new MapPropertySource("customProperties",
35+
Collections.<String, Object>singletonMap("custom.foo",
36+
environment.resolvePlaceholders(
37+
"${spring.application.name:bar}"))));
38+
}
39+
40+
};
41+
}
42+
1743
}

spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java

+34-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.junit.Rule;
2626
import org.junit.Test;
2727
import org.junit.rules.ExpectedException;
28+
2829
import org.springframework.boot.builder.SpringApplicationBuilder;
2930
import org.springframework.boot.context.properties.ConfigurationProperties;
3031
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -54,6 +55,8 @@ public class BootstrapConfigurationTests {
5455

5556
private ConfigurableApplicationContext context;
5657

58+
private ConfigurableApplicationContext sibling;
59+
5760
@Rule
5861
public ExpectedException expected = ExpectedException.none();
5962

@@ -68,6 +71,9 @@ public void close() {
6871
if (this.context != null) {
6972
this.context.close();
7073
}
74+
if (this.sibling != null) {
75+
this.sibling.close();
76+
}
7177
}
7278

7379
@Test
@@ -197,7 +203,7 @@ public void overrideAllWhenOverrideAllowed() {
197203
PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "true");
198204
ConfigurableEnvironment environment = new StandardEnvironment();
199205
environment.getPropertySources().addLast(new MapPropertySource("last",
200-
Collections.<String, Object> singletonMap("bootstrap.foo", "splat")));
206+
Collections.<String, Object>singletonMap("bootstrap.foo", "splat")));
201207
this.context = new SpringApplicationBuilder().web(false).environment(environment)
202208
.sources(BareConfiguration.class).run();
203209
assertEquals("splat", this.context.getEnvironment().getProperty("bootstrap.foo"));
@@ -282,6 +288,32 @@ public void onlyOneBootstrapContext() {
282288
assertNotNull(context.getParent());
283289
assertEquals("bootstrap", context.getParent().getParent().getId());
284290
assertNull(context.getParent().getParent().getParent());
291+
assertEquals("bar", context.getEnvironment().getProperty("custom.foo"));
292+
}
293+
294+
@Test
295+
public void bootstrapContextSharedBySiblings() {
296+
TestHigherPriorityBootstrapConfiguration.count.set(0);
297+
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
298+
SpringApplicationBuilder builder = new SpringApplicationBuilder()
299+
.sources(BareConfiguration.class);
300+
this.sibling = builder.child(BareConfiguration.class)
301+
.properties("spring.application.name=sibling").web(false).run();
302+
this.context = builder.child(BareConfiguration.class)
303+
.properties("spring.application.name=context").web(false).run();
304+
assertEquals(1, TestHigherPriorityBootstrapConfiguration.count.get());
305+
assertNotNull(context.getParent());
306+
assertEquals("bootstrap", context.getParent().getParent().getId());
307+
assertNull(context.getParent().getParent().getParent());
308+
assertEquals("context", context.getEnvironment().getProperty("custom.foo"));
309+
assertEquals("context",
310+
context.getEnvironment().getProperty("spring.application.name"));
311+
assertNotNull(sibling.getParent());
312+
assertEquals("bootstrap", sibling.getParent().getParent().getId());
313+
assertNull(sibling.getParent().getParent().getParent());
314+
assertEquals("sibling", sibling.getEnvironment().getProperty("custom.foo"));
315+
assertEquals("sibling",
316+
sibling.getEnvironment().getProperty("spring.application.name"));
285317
}
286318

287319
@Test
@@ -340,7 +372,7 @@ protected static class BareConfiguration {
340372
protected static class PropertySourceConfiguration implements PropertySourceLocator {
341373

342374
public static Map<String, Object> MAP = new HashMap<String, Object>(
343-
Collections.<String, Object> singletonMap("bootstrap.foo", "bar"));
375+
Collections.<String, Object>singletonMap("bootstrap.foo", "bar"));
344376

345377
private String name;
346378

0 commit comments

Comments
 (0)