Further paring down of leaky proeprty sources in Bootstrap phase

The biggest problem addressed here is one where an
EnvironmentPostProcessor (reasonably) adds entries to the
defaultProperties in the bootstrap context, but then that
property source is not merged with the parent, or is merged
too late (because it only happens when the application context
parent is set). The result would be that things that were activated
during bootstrap would be not be activated in the main context, or
would be activated too late (early enough for beans to bind to but
not for other listeners and post processors to get access to the
additional properties).

See https://github.com/spring-cloud/spring-cloud-sleuth/issues/126
This commit is contained in:
Dave Syer
2016-01-22 13:04:23 +00:00
parent 532cb8efc7
commit b4c2c14d68
2 changed files with 41 additions and 4 deletions

View File

@@ -108,8 +108,9 @@ public class BootstrapApplicationListener
// TODO: is it possible or sensible to share a ResourceLoader?
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment).registerShutdownHook(false)
.properties("spring.application.name:" + configName).web(false);
.environment(bootstrapEnvironment)
.properties("spring.application.name:" + configName)
.registerShutdownHook(false).web(false);
List<Class<?>> sources = new ArrayList<>();
for (String name : names) {
Class<?> cls = ClassUtils.resolveClassName(name, null);
@@ -129,9 +130,45 @@ public class BootstrapApplicationListener
// It only has properties in it now that we don't want in the parent so remove
// it (and it will be added back later)
bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
return context;
}
private void mergeDefaultProperties(MutablePropertySources environment,
MutablePropertySources bootstrap) {
String name = "defaultProperties";
if (!bootstrap.contains(name)) {
return;
}
PropertySource<?> source = bootstrap.get(name);
if (source instanceof MapPropertySource) {
Map<String, Object> map = ((MapPropertySource) source).getSource();
// The application name is "bootstrap" (by default) at this point and
// we don't want that to appear in the parent context at all.
map.remove("spring.application.name");
}
if (!environment.contains(name)) {
environment.addLast(source);
}
else {
PropertySource<?> target = environment.get(name);
if (target instanceof MapPropertySource) {
Map<String, Object> targetMap = ((MapPropertySource) target).getSource();
if (target == source) {
return;
}
if (source instanceof MapPropertySource) {
Map<String, Object> map = ((MapPropertySource) source).getSource();
for (String key : map.keySet()) {
if (!target.containsProperty(key)) {
targetMap.put(key, map.get(key));
}
}
}
}
}
}
private void addAncestorInitializer(SpringApplication application,
ConfigurableApplicationContext context) {
boolean installed = false;

View File

@@ -212,9 +212,9 @@ public class BootstrapConfigurationTests {
.sources(BareConfiguration.class).run();
assertEquals("main",
this.context.getEnvironment().getProperty("spring.application.name"));
// The parent is called "application" because spring.application.name is not
// The parent has no name because spring.application.name is not
// defined in the bootstrap properties
assertEquals("application", this.context.getParent().getEnvironment()
assertEquals(null, this.context.getParent().getEnvironment()
.getProperty("spring.application.name"));
}