Browse Source

Apply bootstrap initializers to all contexts in a hierarchy

The fix for gh-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 gh-190
pull/497/head
Dave Syer 8 years ago
parent
commit
ba281228f6
  1. 46
      spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java
  2. 26
      spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/TestBootstrapConfiguration.java
  3. 36
      spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java

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

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.cloud.bootstrap;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -46,6 +47,7 @@ import org.springframework.core.env.StandardEnvironment; @@ -46,6 +47,7 @@ import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@ -81,26 +83,56 @@ public class BootstrapApplicationListener @@ -81,26 +83,56 @@ public class BootstrapApplicationListener
if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
}
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication().getInitializers()) {
ConfigurableApplicationContext context = null;
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
.getInitializers()) {
if (initializer instanceof ParentContextApplicationContextInitializer) {
return;
context = findBootstrapContext(
(ParentContextApplicationContextInitializer) initializer,
configName);
}
}
ConfigurableApplicationContext context = bootstrapServiceContext(environment,
event.getSpringApplication());
if (context == null) {
context = bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
}
apply(context, event.getSpringApplication(), environment);
}
private ConfigurableApplicationContext findBootstrapContext(
ParentContextApplicationContextInitializer initializer, String configName) {
Field field = ReflectionUtils
.findField(ParentContextApplicationContextInitializer.class, "parent");
ReflectionUtils.makeAccessible(field);
ConfigurableApplicationContext parent = safeCast(
ConfigurableApplicationContext.class,
ReflectionUtils.getField(field, initializer));
if (parent != null && !configName.equals(parent.getId())) {
parent = safeCast(ConfigurableApplicationContext.class, parent.getParent());
}
return parent;
}
private <T> T safeCast(Class<T> type, Object object) {
try {
return type.cast(object);
}
catch (ClassCastException e) {
return null;
}
}
private ConfigurableApplicationContext bootstrapServiceContext(
ConfigurableEnvironment environment, final SpringApplication application) {
ConfigurableEnvironment environment, final SpringApplication application,
String configName) {
StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
MutablePropertySources bootstrapProperties = bootstrapEnvironment
.getPropertySources();
for (PropertySource<?> source : bootstrapProperties) {
bootstrapProperties.remove(source.getName());
}
String configName = environment
.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
String configLocation = environment
.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
Map<String, Object> bootstrapMap = new HashMap<>();

26
spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/TestBootstrapConfiguration.java

@ -1,6 +1,14 @@ @@ -1,6 +1,14 @@
package org.springframework.cloud.bootstrap;
import java.util.Collections;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import static org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration.firstToBeCreated;
@ -8,10 +16,28 @@ import static org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapCon @@ -8,10 +16,28 @@ import static org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapCon
* @author Spencer Gibb
*/
@Order(0)
@Configuration
public class TestBootstrapConfiguration {
public TestBootstrapConfiguration() {
firstToBeCreated.compareAndSet(null, TestBootstrapConfiguration.class);
}
@Bean
public ApplicationContextInitializer<ConfigurableApplicationContext> customInitializer() {
return new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.getPropertySources()
.addLast(new MapPropertySource("customProperties",
Collections.<String, Object>singletonMap("custom.foo",
environment.resolvePlaceholders(
"${spring.application.name:bar}"))));
}
};
}
}

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

@ -25,6 +25,7 @@ import org.junit.After; @@ -25,6 +25,7 @@ import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -54,6 +55,8 @@ public class BootstrapConfigurationTests { @@ -54,6 +55,8 @@ public class BootstrapConfigurationTests {
private ConfigurableApplicationContext context;
private ConfigurableApplicationContext sibling;
@Rule
public ExpectedException expected = ExpectedException.none();
@ -68,6 +71,9 @@ public class BootstrapConfigurationTests { @@ -68,6 +71,9 @@ public class BootstrapConfigurationTests {
if (this.context != null) {
this.context.close();
}
if (this.sibling != null) {
this.sibling.close();
}
}
@Test
@ -197,7 +203,7 @@ public class BootstrapConfigurationTests { @@ -197,7 +203,7 @@ public class BootstrapConfigurationTests {
PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "true");
ConfigurableEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addLast(new MapPropertySource("last",
Collections.<String, Object> singletonMap("bootstrap.foo", "splat")));
Collections.<String, Object>singletonMap("bootstrap.foo", "splat")));
this.context = new SpringApplicationBuilder().web(false).environment(environment)
.sources(BareConfiguration.class).run();
assertEquals("splat", this.context.getEnvironment().getProperty("bootstrap.foo"));
@ -282,6 +288,32 @@ public class BootstrapConfigurationTests { @@ -282,6 +288,32 @@ public class BootstrapConfigurationTests {
assertNotNull(context.getParent());
assertEquals("bootstrap", context.getParent().getParent().getId());
assertNull(context.getParent().getParent().getParent());
assertEquals("bar", context.getEnvironment().getProperty("custom.foo"));
}
@Test
public void bootstrapContextSharedBySiblings() {
TestHigherPriorityBootstrapConfiguration.count.set(0);
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.sources(BareConfiguration.class);
this.sibling = builder.child(BareConfiguration.class)
.properties("spring.application.name=sibling").web(false).run();
this.context = builder.child(BareConfiguration.class)
.properties("spring.application.name=context").web(false).run();
assertEquals(1, TestHigherPriorityBootstrapConfiguration.count.get());
assertNotNull(context.getParent());
assertEquals("bootstrap", context.getParent().getParent().getId());
assertNull(context.getParent().getParent().getParent());
assertEquals("context", context.getEnvironment().getProperty("custom.foo"));
assertEquals("context",
context.getEnvironment().getProperty("spring.application.name"));
assertNotNull(sibling.getParent());
assertEquals("bootstrap", sibling.getParent().getParent().getId());
assertNull(sibling.getParent().getParent().getParent());
assertEquals("sibling", sibling.getEnvironment().getProperty("custom.foo"));
assertEquals("sibling",
sibling.getEnvironment().getProperty("spring.application.name"));
}
@Test
@ -340,7 +372,7 @@ public class BootstrapConfigurationTests { @@ -340,7 +372,7 @@ public class BootstrapConfigurationTests {
protected static class PropertySourceConfiguration implements PropertySourceLocator {
public static Map<String, Object> MAP = new HashMap<String, Object>(
Collections.<String, Object> singletonMap("bootstrap.foo", "bar"));
Collections.<String, Object>singletonMap("bootstrap.foo", "bar"));
private String name;

Loading…
Cancel
Save