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
This commit is contained in:
Phillip Webb
2017-05-15 22:15:32 -07:00
parent 21a2ba176b
commit 1d06849607
3 changed files with 96 additions and 12 deletions

View File

@@ -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<ConfigurationPropertySource> 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() {

View File

@@ -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}.
* <p>
* 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<ConfigurationPropertySource> from(
MutablePropertySources sources) {
return new SpringConfigurationPropertySources(sources);
}
/**
* Return {@link Iterable} containing new {@link ConfigurationPropertySource}
* instances adapted from the given Spring {@link PropertySource PropertySources}.

View File

@@ -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<String, Object> source = new LinkedHashMap<String, Object>();
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.<String, Object>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 {