From 1d0684960781061e492e97cfe68ba91055a3e498 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 15 May 2017 22:15:32 -0700 Subject: [PATCH] Support rebind on ConfigurationProperties Update `ConfigurationPropertiesBindingPostProcessor` to once again allow beans to be bound again following changes to the underlying property sources. Fixes gh-9160 --- ...urationPropertiesBindingPostProcessor.java | 32 ++++++---- .../source/ConfigurationPropertySources.java | 16 +++++ ...onPropertiesBindingPostProcessorTests.java | 60 +++++++++++++++++++ 3 files changed, 96 insertions(+), 12 deletions(-) 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 3993358c83..306d3171c5 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 @@ -37,6 +37,7 @@ import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; +import org.springframework.boot.context.properties.bind.convert.BinderConversionService; import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler; import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler; @@ -60,6 +61,7 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.StandardEnvironment; @@ -121,7 +123,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc private Iterable configurationSources; - private Binder binder; + private BinderConversionService binderConversionService; /** * A list of custom converters (in addition to the defaults) to use when converting @@ -222,8 +224,14 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); } - this.configurationSources = ConfigurationPropertySources - .from(this.propertySources); + if (this.propertySources instanceof MutablePropertySources) { + this.configurationSources = ConfigurationPropertySources + .from((MutablePropertySources) this.propertySources); + } + else { + this.configurationSources = ConfigurationPropertySources + .from(this.propertySources); + } } @Override @@ -317,7 +325,9 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc private void postProcessBeforeInitialization(Object bean, String beanName, ConfigurationProperties annotation) { - Binder binder = getBinder(); + Binder binder = new Binder(this.configurationSources, + new PropertySourcesPlaceholdersResolver(this.propertySources), + getBinderConversionService()); Validator validator = determineValidator(bean); BindHandler handler = getBindHandler(annotation, validator); Bindable bindable = Bindable.ofInstance(bean); @@ -331,19 +341,17 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc } } - private Binder getBinder() { - Binder binder = this.binder; - if (binder == null) { + private BinderConversionService getBinderConversionService() { + BinderConversionService binderConversionService = this.binderConversionService; + if (binderConversionService == null) { ConversionService conversionService = this.conversionService; if (conversionService == null) { conversionService = getDefaultConversionService(); } - binder = new Binder(this.configurationSources, - new PropertySourcesPlaceholdersResolver(this.propertySources), - conversionService); - this.binder = binder; + binderConversionService = new BinderConversionService(conversionService); + this.binderConversionService = binderConversionService; } - return binder; + return binderConversionService; } private ConversionService getDefaultConversionService() { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java index 798f853103..3c1f3e7f3e 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java @@ -101,6 +101,22 @@ public final class ConfigurationPropertySources { return Collections.singleton(SpringConfigurationPropertySource.from(source)); } + /** + * Return {@link Iterable} containing new {@link ConfigurationPropertySource} + * instances adapted from the given Spring {@link MutablePropertySources}. + *

+ * This method will flatten any nested property sources and will filter all + * {@link StubPropertySource stub property sources}. Updates to the underlying source + * will be automatically tracked. + * @param sources the Spring property sources to adapt + * @return an {@link Iterable} containing a single newly adapted + * {@link SpringConfigurationPropertySource} instances + */ + public static Iterable from( + MutablePropertySources sources) { + return new SpringConfigurationPropertySources(sources); + } + /** * Return {@link Iterable} containing new {@link ConfigurationPropertySource} * instances adapted from the given Spring {@link PropertySource PropertySources}. 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 67a649e5a0..4ddb076618 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 @@ -18,6 +18,7 @@ package org.springframework.boot.context.properties; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,6 +43,7 @@ import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -417,6 +419,27 @@ public class ConfigurationPropertiesBindingPostProcessorTests { assertThat(foo).isEqualTo(10); } + @Test + public void rebindableConfigurationProperties() throws Exception { + // gh-9160 + this.context = new AnnotationConfigApplicationContext(); + MutablePropertySources sources = this.context.getEnvironment() + .getPropertySources(); + Map source = new LinkedHashMap(); + source.put("example.one", "foo"); + sources.addFirst(new MapPropertySource("test-source", source)); + this.context.register(PrototypePropertiesConfig.class); + this.context.refresh(); + PrototypeBean first = this.context.getBean(PrototypeBean.class); + assertThat(first.getOne()).isEqualTo("foo"); + source.put("example.one", "bar"); + sources.addFirst(new MapPropertySource("extra", + Collections.singletonMap("example.two", "baz"))); + PrototypeBean second = this.context.getBean(PrototypeBean.class); + assertThat(second.getOne()).isEqualTo("bar"); + assertThat(second.getTwo()).isEqualTo("baz"); + } + private void assertBindingFailure(int errorCount) { try { this.context.refresh(); @@ -798,6 +821,43 @@ public class ConfigurationPropertiesBindingPostProcessorTests { } + @Configuration + @EnableConfigurationProperties + public static class PrototypePropertiesConfig { + + @Bean + @Scope("prototype") + @ConfigurationProperties("example") + public PrototypeBean prototypeBean() { + return new PrototypeBean(); + } + + } + + public static class PrototypeBean { + + private String one; + + private String two; + + public String getOne() { + return this.one; + } + + public void setOne(String one) { + this.one = one; + } + + public String getTwo() { + return this.two; + } + + public void setTwo(String two) { + this.two = two; + } + + } + @Configuration @EnableConfigurationProperties public static class ConfigurationPropertiesWithFactoryBean {