From 12b9bfbe1250e762ac5db00368492fb1e9f69d28 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Sat, 23 Jan 2016 10:05:47 +0000 Subject: [PATCH] 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. --- .../BootstrapApplicationListener.java | 106 +++++++++++++++++- .../config/BootstrapConfigurationTests.java | 20 +++- 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java index 8cf84804..ef243173 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapApplicationListener.java @@ -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 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 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 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 findMap(PropertySource propertySource) { + if (propertySource instanceof MapPropertySource) { + return (Map) propertySource.getSource(); + } + return new LinkedHashMap(); + } + + } + } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java index 93bcd7a8..fdb7af3e 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java @@ -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() { + @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"));