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:
@@ -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>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user