Extract file-based property sources during bootstrap

The bootstrap.properties need to stick with the default properties
during the process of initializing the application.properties
otherwise the ordering ends up wrong because of the addLast()
semantics in an Environment merge. To do this is a bit ugly with
the additional constraint that the default properties has to
remain a MapPropertySource (so that other processors can append
to it if needed). So we created a custom extension of
MapPropertySource that also carries all the bootstrap properties
during the phase where the application.properties are being
processed, but unpacks them as soon as possible afterwards
in an ApplicationContextInitializer, preserving the order, but
making the bootstrap.properties available effectively for the
whole of the startup and initializaion phase of the main context.
This commit is contained in:
Dave Syer
2016-01-23 10:05:47 +00:00
parent b4c2c14d68
commit 12b9bfbe12
2 changed files with 124 additions and 2 deletions

View File

@@ -17,7 +17,9 @@
package org.springframework.cloud.bootstrap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -34,7 +36,9 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
@@ -61,6 +65,8 @@ public class BootstrapApplicationListener
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
public static final String DEFAULT_PROPERTIES = "defaultProperties";
private int order = DEFAULT_ORDER;
@Override
@@ -136,7 +142,7 @@ public class BootstrapApplicationListener
private void mergeDefaultProperties(MutablePropertySources environment,
MutablePropertySources bootstrap) {
String name = "defaultProperties";
String name = DEFAULT_PROPERTIES;
if (!bootstrap.contains(name)) {
return;
}
@@ -167,6 +173,26 @@ public class BootstrapApplicationListener
}
}
}
mergeAdditionalPropertySources(environment, bootstrap);
}
private void mergeAdditionalPropertySources(MutablePropertySources environment,
MutablePropertySources bootstrap) {
PropertySource<?> defaultProperties = environment.get(DEFAULT_PROPERTIES);
ExtendedDefaultPropertySource result = defaultProperties instanceof ExtendedDefaultPropertySource
? (ExtendedDefaultPropertySource) defaultProperties
: new ExtendedDefaultPropertySource(defaultProperties.getName(),
defaultProperties);
for (PropertySource<?> source : bootstrap) {
if (!environment.contains(source.getName())) {
result.add(source);
}
}
for (String name : result.getPropertySourceNames()) {
bootstrap.remove(name);
}
environment.replace(DEFAULT_PROPERTIES, result);
bootstrap.replace(DEFAULT_PROPERTIES, result);
}
private void addAncestorInitializer(SpringApplication application,
@@ -258,10 +284,26 @@ public class BootstrapApplicationListener
while (context.getParent() != null && context.getParent() != context) {
context = (ConfigurableApplicationContext) context.getParent();
}
reorderSources(context.getEnvironment());
new ParentContextApplicationContextInitializer(this.parent)
.initialize(context);
}
private void reorderSources(ConfigurableEnvironment environment) {
PropertySource<?> removed = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES);
if (removed instanceof ExtendedDefaultPropertySource) {
ExtendedDefaultPropertySource defaultProperties = (ExtendedDefaultPropertySource) removed;
environment.getPropertySources().addLast(new MapPropertySource(
DEFAULT_PROPERTIES, defaultProperties.getSource()));
for (PropertySource<?> source : defaultProperties.getPropertySources()
.getPropertySources()) {
environment.getPropertySources().addBefore(DEFAULT_PROPERTIES,
source);
}
}
}
}
/**
@@ -285,4 +327,66 @@ public class BootstrapApplicationListener
}
}
private static class ExtendedDefaultPropertySource extends MapPropertySource {
private final CompositePropertySource sources;
private final List<String> names = new ArrayList<>();
public ExtendedDefaultPropertySource(String name,
PropertySource<?> propertySource) {
super(name, findMap(propertySource));
this.sources = new CompositePropertySource(name);
}
public CompositePropertySource getPropertySources() {
return this.sources;
}
public List<String> getPropertySourceNames() {
return this.names;
}
public void add(PropertySource<?> source) {
if (source instanceof EnumerablePropertySource
&& !this.names.contains(source.getName())) {
this.sources.addPropertySource(source);
this.names.add(source.getName());
}
}
@Override
public Object getProperty(String name) {
if (this.sources.containsProperty(name)) {
return this.sources.getProperty(name);
}
return super.getProperty(name);
}
@Override
public boolean containsProperty(String name) {
if (this.sources.containsProperty(name)) {
return true;
}
return super.containsProperty(name);
}
@Override
public String[] getPropertyNames() {
List<String> names = new ArrayList<>();
names.addAll(Arrays.asList(this.sources.getPropertyNames()));
names.addAll(Arrays.asList(super.getPropertyNames()));
return names.toArray(new String[0]);
}
@SuppressWarnings("unchecked")
private static Map<String, Object> findMap(PropertySource<?> propertySource) {
if (propertySource instanceof MapPropertySource) {
return (Map<String, Object>) propertySource.getSource();
}
return new LinkedHashMap<String, Object>();
}
}
}

View File

@@ -28,6 +28,7 @@ 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;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
@@ -81,6 +82,24 @@ public class BootstrapConfigurationTests {
this.context.getEnvironment().getPropertySources().contains("bootstrap"));
}
@Test
public void bootstrapPropertiesAvailableInInitializer() {
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).initializers(
new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(
ConfigurableApplicationContext applicationContext) {
// This property is defined in bootstrap.properties
assertEquals("child", applicationContext.getEnvironment()
.getProperty("info.name"));
}
})
.run();
assertTrue(
this.context.getEnvironment().getPropertySources().contains("bootstrap"));
}
/**
* Running the test from maven will start from a different directory then starting it
* from intellij
@@ -105,7 +124,6 @@ public class BootstrapConfigurationTests {
@Test
public void picksUpAdditionalPropertySource() {
PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar");
System.setProperty("expected.name", "bootstrap");
this.context = new SpringApplicationBuilder().web(false)
.sources(BareConfiguration.class).run();
assertEquals("bar", this.context.getEnvironment().getProperty("bootstrap.foo"));