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