From 5f8c1e77cc79627495b0f265ac4fa255c1b6d97b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 5 Aug 2014 14:15:36 +0100 Subject: [PATCH] Merge config from custom locations with default configuration Previously, when one or more custom locations were specified on @ConfigurationProperties, the configuration loaded from those locations was used in isolation from the default configuration provided by the environment. Users have been surprised by this behaviour. For example, it means that a placeholder used in the custom configuration will not be resolved against the system properties. This commit adds a new attribute, merge, to @ConfigurationProperties, that defaults to true. When merge is true the default property sources are appended to those that are loaded from the custom locations. When set to false the custom configuration is used in isolation. Closes #1301 --- .../properties/ConfigurationProperties.java | 14 ++++- ...urationPropertiesBindingPostProcessor.java | 15 ++++-- ...onPropertiesBindingPostProcessorTests.java | 52 +++++++++++++++++++ .../src/test/resources/custom-location.yml | 1 + 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 spring-boot/src/test/resources/custom-location.yml diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java index 6da6fca3891..ca780fe2e19 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationProperties.java @@ -78,10 +78,20 @@ public @interface ConfigurationProperties { boolean exceptionIfInvalid() default true; /** - * Optionally provide an explicit resource locations to bind to instead of using the - * default environment. + * Optionally provide explicit resource locations to bind to. By default the + * configuration at these specified locations will be merged with the default + * configuration. * @return the path (or paths) of resources to bind to + * @see #merge() */ String[] locations() default {}; + /** + * Flag to indicate that configuration loaded from the specified locations should be + * merged with the default configuration. + * @return the flag value (default true) + * @see #locations() + */ + boolean merge() default true; + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index 6dff66b337b..dd4876b8d66 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -262,7 +262,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc PropertiesConfigurationFactory factory = new PropertiesConfigurationFactory( target); if (annotation != null && annotation.locations().length != 0) { - factory.setPropertySources(loadPropertySources(annotation.locations())); + factory.setPropertySources(loadPropertySources(annotation.locations(), + annotation.merge())); } else { factory.setPropertySources(this.propertySources); @@ -301,7 +302,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc return this.validator; } - private PropertySources loadPropertySources(String[] locations) { + private PropertySources loadPropertySources(String[] locations, + boolean mergeDefaultSources) { try { PropertySourcesLoader loader = new PropertySourcesLoader(); for (String location : locations) { @@ -314,7 +316,14 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc } loader.load(resource); } - return loader.getPropertySources(); + + MutablePropertySources loaded = loader.getPropertySources(); + if (mergeDefaultSources) { + for (PropertySource propertySource : this.propertySources) { + loaded.addLast(propertySource); + } + } + return loaded; } catch (IOException ex) { throw new IllegalStateException(ex); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java index 50c55a0e9ae..58367b127fb 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessorTests.java @@ -141,6 +141,26 @@ public class ConfigurationPropertiesBindingPostProcessorTests { equalTo("foo")); } + @Test + public void placeholderResolutionWithCustomLocation() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "fooValue:bar"); + this.context.register(CustomConfigurationLocation.class); + this.context.refresh(); + assertThat(this.context.getBean(CustomConfigurationLocation.class).getFoo(), + equalTo("bar")); + } + + @Test + public void placeholderResolutionWithUnmergedCustomLocation() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, "fooValue:bar"); + this.context.register(UnmergedCustomConfigurationLocation.class); + this.context.refresh(); + assertThat(this.context.getBean(UnmergedCustomConfigurationLocation.class) + .getFoo(), equalTo("${fooValue}")); + } + @Configuration @EnableConfigurationProperties public static class TestConfigurationWithValidatingSetter { @@ -299,4 +319,36 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @EnableConfigurationProperties + @ConfigurationProperties(locations = "custom-location.yml") + public static class CustomConfigurationLocation { + + private String foo; + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + + @EnableConfigurationProperties + @ConfigurationProperties(locations = "custom-location.yml", merge = false) + public static class UnmergedCustomConfigurationLocation { + + private String foo; + + public String getFoo() { + return this.foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + } + } diff --git a/spring-boot/src/test/resources/custom-location.yml b/spring-boot/src/test/resources/custom-location.yml new file mode 100644 index 00000000000..60000ea2327 --- /dev/null +++ b/spring-boot/src/test/resources/custom-location.yml @@ -0,0 +1 @@ +foo: ${fooValue} \ No newline at end of file