Only use Converters which are @ConfigurationPropertiesBinder qualified
Users sometimes create beans of type Converter and don't expect that to automatically trigger a cascade of early initialization. This change adds a qualifier to the Converters that are used by @ConfigurationProperties, so they can be isolated (and simple). Fixes gh-2669
This commit is contained in:
parent
538afc4ab1
commit
67483bb73c
|
@ -761,7 +761,8 @@ The following properties names can all be used:
|
|||
Spring will attempt to coerce the external application properties to the right type when
|
||||
it binds to the `@ConfigurationProperties` beans. If you need custom type conversion you
|
||||
can provide a `ConversionService` bean (with bean id `conversionService`) or custom
|
||||
property editors (via a `CustomEditorConfigurer` bean).
|
||||
property editors (via a `CustomEditorConfigurer` bean) or custom `Converters` (with
|
||||
bean definitions annotated as `@ConfigurationPropertiesBinding`).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Qualifier for beans that are needed to configure the binding of
|
||||
* {@link ConfigurationProperties} (e.g. Converters).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ConfigurationPropertiesBinding {
|
||||
}
|
|
@ -17,7 +17,9 @@
|
|||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
|
@ -28,6 +30,7 @@ import org.springframework.beans.factory.DisposableBean;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.bind.PropertiesConfigurationFactory;
|
||||
import org.springframework.boot.env.PropertySourcesLoader;
|
||||
|
@ -69,13 +72,13 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
|||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
|
||||
BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, ApplicationContextAware,
|
||||
InitializingBean, DisposableBean, PriorityOrdered {
|
||||
BeanFactoryAware, ResourceLoaderAware, EnvironmentAware, ApplicationContextAware,
|
||||
InitializingBean, DisposableBean, PriorityOrdered {
|
||||
|
||||
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
|
||||
|
||||
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
|
||||
"javax.validation.ValidatorFactory" };
|
||||
"javax.validation.ValidatorFactory" };
|
||||
|
||||
private ConfigurationBeanFactoryMetaData beans = new ConfigurationBeanFactoryMetaData();
|
||||
|
||||
|
@ -97,8 +100,21 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private List<Converter<?, ?>> converters = Collections.emptyList();
|
||||
|
||||
private int order = Ordered.HIGHEST_PRECEDENCE + 1;
|
||||
|
||||
/**
|
||||
* A list of custom converters (in addition to the defaults) to use when
|
||||
* converting properties for binding.
|
||||
* @param converters the converters to set
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
@ConfigurationPropertiesBinding
|
||||
public void setConverters(List<Converter<?, ?>> converters) {
|
||||
this.converters = converters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param order the order to set
|
||||
*/
|
||||
|
@ -246,8 +262,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
|
||||
bean.getClass(), ConfigurationProperties.class);
|
||||
ConfigurationProperties annotation = AnnotationUtils
|
||||
.findAnnotation(bean.getClass(), ConfigurationProperties.class);
|
||||
if (annotation != null || bean instanceof ConfigurationPropertiesHolder) {
|
||||
postProcessBeforeInitialization(bean, beanName, annotation);
|
||||
}
|
||||
|
@ -267,13 +283,13 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
|
||||
private void postProcessBeforeInitialization(Object bean, String beanName,
|
||||
ConfigurationProperties annotation) {
|
||||
Object target = (bean instanceof ConfigurationPropertiesHolder ? ((ConfigurationPropertiesHolder) bean)
|
||||
.getTarget() : bean);
|
||||
Object target = (bean instanceof ConfigurationPropertiesHolder
|
||||
? ((ConfigurationPropertiesHolder) bean).getTarget() : bean);
|
||||
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
|
||||
target);
|
||||
if (annotation != null && annotation.locations().length != 0) {
|
||||
factory.setPropertySources(loadPropertySources(annotation.locations(),
|
||||
annotation.merge()));
|
||||
factory.setPropertySources(
|
||||
loadPropertySources(annotation.locations(), annotation.merge()));
|
||||
}
|
||||
else {
|
||||
factory.setPropertySources(this.propertySources);
|
||||
|
@ -281,15 +297,15 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
factory.setValidator(determineValidator(bean));
|
||||
// If no explicit conversion service is provided we add one so that (at least)
|
||||
// comma-separated arrays of convertibles can be bound automatically
|
||||
factory.setConversionService(this.conversionService == null ? getDefaultConversionService()
|
||||
: this.conversionService);
|
||||
factory.setConversionService(this.conversionService == null
|
||||
? getDefaultConversionService() : this.conversionService);
|
||||
if (annotation != null) {
|
||||
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
|
||||
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
|
||||
factory.setExceptionIfInvalid(annotation.exceptionIfInvalid());
|
||||
factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
|
||||
String targetName = (StringUtils.hasLength(annotation.value()) ? annotation
|
||||
.value() : annotation.prefix());
|
||||
String targetName = (StringUtils.hasLength(annotation.value())
|
||||
? annotation.value() : annotation.prefix());
|
||||
if (StringUtils.hasLength(targetName)) {
|
||||
factory.setTargetName(targetName);
|
||||
}
|
||||
|
@ -300,7 +316,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
catch (Exception ex) {
|
||||
String targetClass = ClassUtils.getShortName(target.getClass());
|
||||
throw new BeanCreationException(beanName, "Could not bind properties to "
|
||||
+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
|
||||
+ targetClass + " (" + getAnnotationDetails(annotation) + ")",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -309,19 +326,18 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
return "";
|
||||
}
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append("prefix=").append(
|
||||
(StringUtils.hasLength(annotation.value()) ? annotation.value()
|
||||
: annotation.prefix()));
|
||||
details.append("prefix=").append((StringUtils.hasLength(annotation.value())
|
||||
? annotation.value() : annotation.prefix()));
|
||||
details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
|
||||
details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());
|
||||
details.append(", ignoreNestedProperties=").append(
|
||||
annotation.ignoreNestedProperties());
|
||||
details.append(", ignoreNestedProperties=")
|
||||
.append(annotation.ignoreNestedProperties());
|
||||
return details.toString();
|
||||
}
|
||||
|
||||
private Validator determineValidator(Object bean) {
|
||||
boolean globalValidatorSupportBean = (this.validator != null && this.validator
|
||||
.supports(bean.getClass()));
|
||||
boolean globalValidatorSupportBean = (this.validator != null
|
||||
&& this.validator.supports(bean.getClass()));
|
||||
if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {
|
||||
if (!globalValidatorSupportBean) {
|
||||
return (Validator) bean;
|
||||
|
@ -336,8 +352,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
try {
|
||||
PropertySourcesLoader loader = new PropertySourcesLoader();
|
||||
for (String location : locations) {
|
||||
Resource resource = this.resourceLoader.getResource(this.environment
|
||||
.resolvePlaceholders(location));
|
||||
Resource resource = this.resourceLoader
|
||||
.getResource(this.environment.resolvePlaceholders(location));
|
||||
String[] profiles = this.environment.getActiveProfiles();
|
||||
for (int i = profiles.length; i-- > 0;) {
|
||||
String profile = profiles[i];
|
||||
|
@ -361,8 +377,9 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
private ConversionService getDefaultConversionService() {
|
||||
if (this.defaultConversionService == null) {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
for (Converter<?, ?> converter : ((ListableBeanFactory) this.beanFactory)
|
||||
.getBeansOfType(Converter.class, false, false).values()) {
|
||||
this.applicationContext.getAutowireCapableBeanFactory()
|
||||
.autowireBean(this);
|
||||
for (Converter<?, ?> converter : this.converters) {
|
||||
conversionService.addConverter(converter);
|
||||
}
|
||||
this.defaultConversionService = conversionService;
|
||||
|
@ -371,8 +388,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
}
|
||||
|
||||
/**
|
||||
* Factory to create JSR 303 LocalValidatorFactoryBean. Inner class to prevent class
|
||||
* loader issues.
|
||||
* Factory to create JSR 303 LocalValidatorFactoryBean. Inner class to prevent
|
||||
* class loader issues.
|
||||
*/
|
||||
private static class Jsr303ValidatorFactory {
|
||||
|
||||
|
@ -386,8 +403,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
}
|
||||
|
||||
/**
|
||||
* {@link Validator} implementation that wraps {@link Validator} instances and chains
|
||||
* their execution.
|
||||
* {@link Validator} implementation that wraps {@link Validator} instances and
|
||||
* chains their execution.
|
||||
*/
|
||||
private static class ChainingValidator implements Validator {
|
||||
|
||||
|
@ -421,7 +438,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
|
|||
|
||||
/**
|
||||
* Convenience class to flatten out a tree of property sources without losing the
|
||||
* reference to the backing data (which can therefore be updated in the background).
|
||||
* reference to the backing data (which can therefore be updated in the
|
||||
* background).
|
||||
*/
|
||||
private static class FlatPropertySources implements PropertySources {
|
||||
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.bind;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.bind.ConverterBindingTests.TestConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationProperties} binding with custom converters.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(TestConfig.class)
|
||||
@IntegrationTest("foo=bar")
|
||||
public class ConverterBindingTests {
|
||||
|
||||
@Value("${foo:}")
|
||||
private String foo;
|
||||
|
||||
@Autowired
|
||||
private Wrapper properties;
|
||||
|
||||
@Test
|
||||
public void overridingOfPropertiesOrderOfAtPropertySources() {
|
||||
assertThat(this.properties.getFoo().getName(), is(this.foo));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(Wrapper.class)
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
public Converter<String, Foo> converter() {
|
||||
return new Converter<String, ConverterBindingTests.Foo>() {
|
||||
|
||||
@Override
|
||||
public Foo convert(String source) {
|
||||
Foo foo = new Foo();
|
||||
foo.setName(source);
|
||||
return foo;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Foo {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class Wrapper {
|
||||
private Foo foo;
|
||||
|
||||
public Foo getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(Foo foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue