Create a public API to bind a ConfigurationProperties object

Previously, the API used to bind a ConfigurationProperties annotated
object was private to the BeanPostProcessor implementation.

This commit moves most of the logic to ConfigurationPropertiesBinder. As
we want this object to have the same state regardless of how it is built,
a builder is now provided that detects the components that the binder
needs if not specified explicitly.

Closes gh-8344
pull/10168/head
Stephane Nicoll 7 years ago
parent 9242def4c0
commit 67fc5ca433

@ -0,0 +1,202 @@
/*
* Copyright 2012-2017 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 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.handler.IgnoreErrorsBindHandler;
import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* Bind {@link ConfigurationProperties} annotated object from a configurable list of
* {@link PropertySource}.
*
* @author Stephane Nicoll
* @since 2.0.0
* @see ConfigurationPropertiesBinderBuilder
*/
public class ConfigurationPropertiesBinder {
private final Iterable<PropertySource<?>> propertySources;
private final ConversionService conversionService;
private final Validator validator;
private Iterable<ConfigurationPropertySource> configurationSources;
ConfigurationPropertiesBinder(Iterable<PropertySource<?>> propertySources,
ConversionService conversionService, Validator validator) {
Assert.notNull(propertySources, "PropertySources must not be null");
this.propertySources = propertySources;
this.conversionService = conversionService;
this.validator = validator;
if (propertySources instanceof MutablePropertySources) {
this.configurationSources = ConfigurationPropertySources
.from((MutablePropertySources) propertySources);
}
else {
this.configurationSources = ConfigurationPropertySources
.from(propertySources);
}
}
/**
* Bind the specified {@code target} object if it is annotated with
* {@link ConfigurationProperties}, otherwise ignore it.
* @param target the target to bind the configuration property sources to
* @throws ConfigurationPropertiesBindingException if the binding failed
*/
public void bind(Object target) {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
target.getClass(), ConfigurationProperties.class);
if (annotation != null) {
bind(target, annotation);
}
}
/**
* Bind the specified {@code target} object using the configuration defined by
* the specified {@code annotation}.
* @param target the target to bind the configuration property sources to
* @param annotation the binding configuration
* @throws ConfigurationPropertiesBindingException if the binding failed
*/
void bind(Object target, ConfigurationProperties annotation) {
Binder binder = new Binder(this.configurationSources,
new PropertySourcesPlaceholdersResolver(this.propertySources),
this.conversionService);
Validator validator = determineValidator(target);
BindHandler handler = getBindHandler(annotation, validator);
Bindable<?> bindable = Bindable.ofInstance(target);
try {
binder.bind(annotation.prefix(), bindable, handler);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindingException(target.getClass(),
getAnnotationDetails(annotation), ex);
}
}
/**
* Destroy this binder instance.
*/
void destroy() {
if (this.validator instanceof InternalValidator) {
((InternalValidator) this.validator).destroy();
}
}
private Validator determineValidator(Object bean) {
boolean supportsBean = (this.validator != null
&& this.validator.supports(bean.getClass()));
if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {
if (supportsBean) {
return new ChainingValidator(this.validator, (Validator) bean);
}
return (Validator) bean;
}
return (supportsBean ? this.validator : null);
}
private BindHandler getBindHandler(ConfigurationProperties annotation,
Validator validator) {
BindHandler handler = BindHandler.DEFAULT;
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (validator != null) {
handler = new ValidationBindHandler(handler, validator);
}
return handler;
}
private String getAnnotationDetails(ConfigurationProperties annotation) {
if (annotation == null) {
return "";
}
StringBuilder details = new StringBuilder();
details.append("prefix=").append(annotation.prefix());
details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());
return details.toString();
}
/**
* {@link Validator} extension to be implemented to signal that that validator can
* be destroyed once the binder is no longer in use.
*/
interface InternalValidator extends Validator {
void destroy();
}
/**
* {@link Validator} implementation that wraps {@link Validator} instances and chains
* their execution.
*/
private static class ChainingValidator implements Validator {
private final Validator[] validators;
ChainingValidator(Validator... validators) {
Assert.notNull(validators, "Validators must not be null");
this.validators = validators;
}
@Override
public boolean supports(Class<?> clazz) {
for (Validator validator : this.validators) {
if (validator.supports(clazz)) {
return true;
}
}
return false;
}
@Override
public void validate(Object target, Errors errors) {
for (Validator validator : this.validators) {
if (validator.supports(target.getClass())) {
validator.validate(target, errors);
}
}
}
}
}

@ -0,0 +1,227 @@
/*
* Copyright 2012-2017 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.util.Collections;
import java.util.List;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
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.PropertySource;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Validator;
/**
* Builder for creating {@link ConfigurationPropertiesBinder} based on the state of
* the {@link ApplicationContext}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class ConfigurationPropertiesBinderBuilder {
/**
* The bean name of the configuration properties validator.
*/
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
private static final String CONVERSION_SERVICE_BEAN_NAME = ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
"javax.validation.ValidatorFactory" };
private final ApplicationContext applicationContext;
private ConversionService conversionService;
private Validator validator;
private Iterable<PropertySource<?>> propertySources;
/**
* Creates an instance with the {@link ApplicationContext} to use.
* @param applicationContext the application context
*/
public ConfigurationPropertiesBinderBuilder(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Specify the {@link ConversionService} to use or {@code null} to use the default.
* <p>
* By default, use a {@link ConversionService} bean named
* {@value #CONVERSION_SERVICE_BEAN_NAME} if any. Otherwise create a
* {@link DefaultConversionService} with any {@link ConfigurationPropertiesBinding}
* qualified {@link Converter} and {@link GenericConverter} beans found in the
* context.
* @param conversionService the conversion service to use or {@code null}
* @return this instance
*/
public ConfigurationPropertiesBinderBuilder withConversionService(
ConversionService conversionService) {
this.conversionService = conversionService;
return this;
}
/**
* Specify the {@link Validator} to use or {@code null} to use the default.
* <p>
* By default, use a {@link Validator} bean named {@value VALIDATOR_BEAN_NAME} if
* any. If not, create a JSR 303 Validator if the necessary libraries are available.
* No validation occurs otherwise.
* @param validator the validator to use or {@code null}
* @return this instance
*/
public ConfigurationPropertiesBinderBuilder withValidator(Validator validator) {
this.validator = validator;
return this;
}
/**
* Specify the {@link PropertySource property sources} to use.
* @param propertySources the configuration the binder should use
* @return this instance
* @see #withEnvironment(ConfigurableEnvironment)
*/
public ConfigurationPropertiesBinderBuilder withPropertySources(
Iterable<PropertySource<?>> propertySources) {
this.propertySources = propertySources;
return this;
}
/**
* Specify the {@link ConfigurableEnvironment Environment} to use, use all
* available {@link PropertySource}.
* @param environment the environment to use
* @return this instance
* @see #withPropertySources(Iterable)
*/
public ConfigurationPropertiesBinderBuilder withEnvironment(
ConfigurableEnvironment environment) {
return withPropertySources(environment.getPropertySources());
}
/**
* Build a {@link ConfigurationPropertiesBinder} based on the state of the builder,
* discovering the {@lik ConversionService} and {@link Validator} if necessary.
* @return a {@link ConfigurationPropertiesBinder}
*/
public ConfigurationPropertiesBinder build() {
return new ConfigurationPropertiesBinder(this.propertySources,
determineConversionService(), determineValidator());
}
private Validator determineValidator() {
if (this.validator != null) {
return this.validator;
}
Validator defaultValidator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class);
if (defaultValidator != null) {
return defaultValidator;
}
if (isJsr303Present()) {
return new ValidatedLocalValidatorFactoryBean(this.applicationContext);
}
return null;
}
private ConversionService determineConversionService() {
if (this.conversionService != null) {
return this.conversionService;
}
ConversionService conversionServiceByName = getOptionalBean(
CONVERSION_SERVICE_BEAN_NAME, ConversionService.class);
if (conversionServiceByName != null) {
return conversionServiceByName;
}
return createDefaultConversionService();
}
private ConversionService createDefaultConversionService() {
ConversionServiceFactory conversionServiceFactory = this.applicationContext
.getAutowireCapableBeanFactory().createBean(ConversionServiceFactory.class);
return conversionServiceFactory.createConversionService();
}
private boolean isJsr303Present() {
for (String validatorClass : VALIDATOR_CLASSES) {
if (!ClassUtils.isPresent(validatorClass,
this.applicationContext.getClassLoader())) {
return false;
}
}
return true;
}
private <T> T getOptionalBean(String name, Class<T> type) {
try {
return this.applicationContext.getBean(name, type);
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
private static class ConversionServiceFactory {
private List<Converter<?, ?>> converters = Collections.emptyList();
private List<GenericConverter> genericConverters = Collections.emptyList();
/**
* 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;
}
/**
* 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 setGenericConverters(List<GenericConverter> converters) {
this.genericConverters = converters;
}
public ConversionService createConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
for (Converter<?, ?> converter : this.converters) {
conversionService.addConverter(converter);
}
for (GenericConverter genericConverter : this.genericConverters) {
conversionService.addConverter(genericConverter);
}
return conversionService;
}
}
}

@ -0,0 +1,56 @@
/*
* Copyright 2012-2017 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 org.springframework.core.NestedExceptionUtils;
import org.springframework.util.ClassUtils;
/**
* Exception thrown when a {@code @ConfigurationProperties} annotated object failed
* to be bound.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class ConfigurationPropertiesBindingException extends RuntimeException {
private final Class<?> targetClass;
public ConfigurationPropertiesBindingException(Class<?> targetClass,
String message, Throwable cause) {
super("Could not bind properties to '" + ClassUtils.getShortName(targetClass)
+ "': " + message, cause);
this.targetClass = targetClass;
}
/**
* Return the target type of the object that failed to be bound.
* @return the target {@link Class}
*/
public Class<?> getTargetClass() {
return this.targetClass;
}
/**
* Retrieve the innermost cause of this exception, if any.
* @return the innermost exception, or {@code null} if none
*/
public Throwable getRootCause() {
return NestedExceptionUtils.getRootCause(this);
}
}

@ -16,8 +16,6 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -30,48 +28,23 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory; 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.beans.factory.config.BeanPostProcessor;
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;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
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.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/** /**
* {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
@ -87,14 +60,6 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean, BeanFactoryAware, EnvironmentAware, ApplicationContextAware, InitializingBean,
DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered { DisposableBean, ApplicationListener<ContextRefreshedEvent>, PriorityOrdered {
/**
* The bean name of the configuration properties validator.
*/
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
"javax.validation.ValidatorFactory" };
private static final Log logger = LogFactory private static final Log logger = LogFactory
.getLog(ConfigurationPropertiesBindingPostProcessor.class); .getLog(ConfigurationPropertiesBindingPostProcessor.class);
@ -104,49 +69,17 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
private Validator validator; private Validator validator;
private volatile Validator localValidator;
private ConversionService conversionService; private ConversionService conversionService;
private DefaultConversionService defaultConversionService;
private BeanFactory beanFactory; private BeanFactory beanFactory;
private Environment environment = new StandardEnvironment(); private Environment environment = new StandardEnvironment();
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private List<Converter<?, ?>> converters = Collections.emptyList();
private List<GenericConverter> genericConverters = Collections.emptyList();
private int order = Ordered.HIGHEST_PRECEDENCE + 1; private int order = Ordered.HIGHEST_PRECEDENCE + 1;
private Iterable<ConfigurationPropertySource> configurationSources; private ConfigurationPropertiesBinder configurationPropertiesBinder;
private BinderConversionService binderConversionService;
/**
* 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;
}
/**
* 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 setGenericConverters(List<GenericConverter> converters) {
this.genericConverters = converters;
}
/** /**
* Set the order of the bean. * Set the order of the bean.
@ -217,45 +150,23 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
if (this.propertySources == null) { if (this.propertySources == null) {
this.propertySources = deducePropertySources(); this.propertySources = deducePropertySources();
} }
if (this.validator == null) {
this.validator = getOptionalBean(VALIDATOR_BEAN_NAME, Validator.class);
}
if (this.conversionService == null) {
this.conversionService = getOptionalBean(
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
ConversionService.class);
}
if (this.propertySources instanceof MutablePropertySources) {
this.configurationSources = ConfigurationPropertySources
.from((MutablePropertySources) this.propertySources);
}
else {
this.configurationSources = ConfigurationPropertySources
.from(this.propertySources);
}
} }
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
freeLocalValidator(); freeBinder();
} }
@Override @Override
public void destroy() throws Exception { public void destroy() {
freeLocalValidator(); freeBinder();
} }
private void freeLocalValidator() { private void freeBinder() {
try { if (this.configurationPropertiesBinder != null) {
Validator validator = this.localValidator; this.configurationPropertiesBinder.destroy();
this.localValidator = null;
if (validator != null) {
((DisposableBean) validator).destroy();
}
}
catch (Exception ex) {
throw new IllegalStateException(ex);
} }
this.configurationPropertiesBinder = null;
} }
private PropertySources deducePropertySources() { private PropertySources deducePropertySources() {
@ -289,33 +200,19 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
return null; return null;
} }
private <T> T getOptionalBean(String name, Class<T> type) {
try {
return this.beanFactory.getBean(name, type);
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException { throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName); ConfigurationProperties annotation = getAnnotation(bean, beanName);
if (annotation != null) { if (annotation != null) {
postProcessBeforeInitialization(bean, beanName, annotation); try {
getBinder().bind(bean, annotation);
} }
return bean; catch (ConfigurationPropertiesBindingException ex) {
throw new BeanCreationException(beanName, ex.getMessage(), ex.getCause());
} }
private ConfigurationProperties getAnnotation(Object bean, String beanName) {
ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (annotation == null) {
annotation = AnnotationUtils.findAnnotation(bean.getClass(),
ConfigurationProperties.class);
} }
return annotation; return bean;
} }
@Override @Override
@ -324,182 +221,24 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
return bean; return bean;
} }
private void postProcessBeforeInitialization(Object bean, String beanName, private ConfigurationProperties getAnnotation(Object bean, String beanName) {
ConfigurationProperties annotation) { ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName,
Binder binder = new Binder(this.configurationSources, ConfigurationProperties.class);
new PropertySourcesPlaceholdersResolver(this.propertySources),
getBinderConversionService());
Validator validator = determineValidator(bean);
BindHandler handler = getBindHandler(annotation, validator);
Bindable<?> bindable = Bindable.ofInstance(bean);
try {
binder.bind(annotation.prefix(), bindable, handler);
}
catch (Exception ex) {
String targetClass = ClassUtils.getShortName(bean.getClass());
throw new BeanCreationException(beanName, "Could not bind properties to "
+ targetClass + " (" + getAnnotationDetails(annotation) + ")", ex);
}
}
private BinderConversionService getBinderConversionService() {
BinderConversionService binderConversionService = this.binderConversionService;
if (binderConversionService == null) {
ConversionService conversionService = this.conversionService;
if (conversionService == null) {
conversionService = getDefaultConversionService();
}
binderConversionService = new BinderConversionService(conversionService);
this.binderConversionService = binderConversionService;
}
return binderConversionService;
}
private ConversionService getDefaultConversionService() {
if (this.defaultConversionService == null) {
DefaultConversionService conversionService = new DefaultConversionService();
this.applicationContext.getAutowireCapableBeanFactory().autowireBean(this);
for (Converter<?, ?> converter : this.converters) {
conversionService.addConverter(converter);
}
for (GenericConverter genericConverter : this.genericConverters) {
conversionService.addConverter(genericConverter);
}
this.defaultConversionService = conversionService;
}
return this.defaultConversionService;
}
private String getAnnotationDetails(ConfigurationProperties annotation) {
if (annotation == null) { if (annotation == null) {
return ""; annotation = AnnotationUtils.findAnnotation(bean.getClass(),
} ConfigurationProperties.class);
StringBuilder details = new StringBuilder();
details.append("prefix=").append(annotation.prefix());
details.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
details.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());
return details.toString();
}
private Validator determineValidator(Object bean) {
Validator validator = getValidator();
boolean supportsBean = (validator != null && validator.supports(bean.getClass()));
if (ClassUtils.isAssignable(Validator.class, bean.getClass())) {
if (supportsBean) {
return new ChainingValidator(validator, (Validator) bean);
}
return (Validator) bean;
}
return (supportsBean ? validator : null);
}
private Validator getValidator() {
if (this.validator != null) {
return this.validator;
}
if (this.localValidator == null && isJsr303Present()) {
this.localValidator = new ValidatedLocalValidatorFactoryBean(
this.applicationContext);
}
return this.localValidator;
}
private boolean isJsr303Present() {
for (String validatorClass : VALIDATOR_CLASSES) {
if (!ClassUtils.isPresent(validatorClass,
this.applicationContext.getClassLoader())) {
return false;
}
}
return true;
}
private BindHandler getBindHandler(ConfigurationProperties annotation,
Validator validator) {
BindHandler handler = BindHandler.DEFAULT;
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (validator != null) {
handler = new ValidationBindHandler(handler, validator);
}
return handler;
}
/**
* {@link LocalValidatorFactoryBean} supports classes annotated with
* {@link Validated @Validated}.
*/
private static class ValidatedLocalValidatorFactoryBean
extends LocalValidatorFactoryBean {
private static final Log logger = LogFactory
.getLog(ConfigurationPropertiesBindingPostProcessor.class);
ValidatedLocalValidatorFactoryBean(ApplicationContext applicationContext) {
setApplicationContext(applicationContext);
setMessageInterpolator(new MessageInterpolatorFactory().getObject());
afterPropertiesSet();
}
@Override
public boolean supports(Class<?> type) {
if (!super.supports(type)) {
return false;
}
if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) {
return true;
}
if (type.getPackage() != null && type.getPackage().getName()
.startsWith("org.springframework.boot")) {
return false;
}
if (getConstraintsForClass(type).isBeanConstrained()) {
logger.warn("The @ConfigurationProperties bean " + type
+ " contains validation constraints but had not been annotated "
+ "with @Validated.");
}
return true;
}
}
/**
* {@link Validator} implementation that wraps {@link Validator} instances and chains
* their execution.
*/
private static class ChainingValidator implements Validator {
private final Validator[] validators;
ChainingValidator(Validator... validators) {
Assert.notNull(validators, "Validators must not be null");
this.validators = validators;
}
@Override
public boolean supports(Class<?> clazz) {
for (Validator validator : this.validators) {
if (validator.supports(clazz)) {
return true;
}
} }
return false; return annotation;
} }
@Override private ConfigurationPropertiesBinder getBinder() {
public void validate(Object target, Errors errors) { if (this.configurationPropertiesBinder == null) {
for (Validator validator : this.validators) { this.configurationPropertiesBinder = new ConfigurationPropertiesBinderBuilder(this.applicationContext)
if (validator.supports(target.getClass())) { .withConversionService(this.conversionService)
validator.validate(target, errors); .withValidator(this.validator)
.withPropertySources(this.propertySources).build();
} }
} return this.configurationPropertiesBinder;
}
} }
} }

@ -0,0 +1,66 @@
/*
* Copyright 2012-2017 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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
/**
* {@link LocalValidatorFactoryBean} supports classes annotated with
* {@link Validated @Validated}.
*
* @author Phillip Webb
*/
class ValidatedLocalValidatorFactoryBean extends LocalValidatorFactoryBean
implements ConfigurationPropertiesBinder.InternalValidator {
private static final Log logger = LogFactory
.getLog(ConfigurationPropertiesBindingPostProcessor.class);
ValidatedLocalValidatorFactoryBean(ApplicationContext applicationContext) {
setApplicationContext(applicationContext);
setMessageInterpolator(new MessageInterpolatorFactory().getObject());
afterPropertiesSet();
}
@Override
public boolean supports(Class<?> type) {
if (!super.supports(type)) {
return false;
}
if (AnnotatedElementUtils.hasAnnotation(type, Validated.class)) {
return true;
}
if (type.getPackage() != null && type.getPackage().getName()
.startsWith("org.springframework.boot")) {
return false;
}
if (getConstraintsForClass(type).isBeanConstrained()) {
logger.warn("The @ConfigurationProperties bean " + type
+ " contains validation constraints but had not been annotated "
+ "with @Validated.");
}
return true;
}
}

@ -0,0 +1,263 @@
/*
* Copyright 2012-2017 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.time.Duration;
import javax.validation.constraints.NotNull;
import org.junit.Test;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.boot.context.properties.bind.validation.ValidationErrors;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ConfigurationPropertiesBinderBuilder}.
*
* @author Stephane Nicoll
*/
public class ConfigurationPropertiesBinderBuilderTests {
private final StaticApplicationContext applicationContext = new StaticApplicationContext();
private final ConfigurationPropertiesBinderBuilder builder =
new ConfigurationPropertiesBinderBuilder(this.applicationContext);
private final MockEnvironment environment = new MockEnvironment();
@Test
public void useCustomConversionService() {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new AddressConverter());
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.address=FooStreet 42");
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment)
.withConversionService(conversionService).build();
PropertyWithAddress target = new PropertyWithAddress();
binder.bind(target);
assertThat(target.getAddress()).isNotNull();
assertThat(target.getAddress().streetName).isEqualTo("FooStreet");
assertThat(target.getAddress().number).isEqualTo(42);
}
@Test
public void detectDefaultConversionService() {
this.applicationContext.registerSingleton("conversionService",
DefaultConversionService.class);
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
assertThat(ReflectionTestUtils.getField(binder, "conversionService")).isSameAs(
this.applicationContext.getBean("conversionService"));
}
@Test
public void bindToJavaTimeDuration() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.duration=PT1M");
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
PropertyWithDuration target = new PropertyWithDuration();
binder.bind(target);
assertThat(target.getDuration().getSeconds()).isEqualTo(60);
}
@Test
public void useCustomValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment)
.withValidator(validator).build();
assertThat(ReflectionTestUtils.getField(binder, "validator")).isSameAs(validator);
}
@Test
public void detectDefaultValidator() {
this.applicationContext.registerSingleton("configurationPropertiesValidator",
LocalValidatorFactoryBean.class);
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
assertThat(ReflectionTestUtils.getField(binder, "validator")).isSameAs(
this.applicationContext.getBean("configurationPropertiesValidator"));
}
@Test
public void validationWithoutJsr303() {
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
assertThat(bindWithValidationErrors(binder, new PropertyWithoutJSR303())
.getAllErrors()).hasSize(1);
}
@Test
public void validationWithJsr303() {
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
assertThat(bindWithValidationErrors(binder, new PropertyWithJSR303())
.getAllErrors()).hasSize(2);
}
@Test
public void validationWithJsr303AndValidInput() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.foo=123456", "test.bar=654321");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithJSR303 target = new PropertyWithJSR303();
binder.bind(target);
assertThat(target.getFoo()).isEqualTo("123456");
assertThat(target.getBar()).isEqualTo("654321");
}
@Test
public void internalValidatorIsClosed() throws Exception {
ConfigurationPropertiesBinder binder = this.builder
.withEnvironment(this.environment).build();
Object validator = ReflectionTestUtils.getField(binder, "validator");
assertThat(validator).isNotNull();
assertThat(validator).isInstanceOf(DisposableBean.class);
DisposableBean validatorSpy = spy((DisposableBean) validator);
ReflectionTestUtils.setField(binder, "validator", validatorSpy);
binder.destroy();
verify(validatorSpy).destroy();
}
private ValidationErrors bindWithValidationErrors(
ConfigurationPropertiesBinder binder, Object target) {
try {
binder.bind(target);
throw new AssertionError("Should have failed to bind " + target);
}
catch (ConfigurationPropertiesBindingException ex) {
Throwable rootCause = ex.getRootCause();
assertThat(rootCause).isInstanceOf(BindValidationException.class);
return ((BindValidationException) rootCause).getValidationErrors();
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithAddress {
private Address address;
public Address getAddress() {
return this.address;
}
public void setAddress(Address address) {
this.address = address;
}
}
private static class Address {
private String streetName;
private Integer number;
Address(String streetName, Integer number) {
this.streetName = streetName;
this.number = number;
}
}
private static class AddressConverter implements Converter<String, Address> {
@Override
public Address convert(String source) {
String[] split = StringUtils.split(source, " ");
return new Address(split[0], Integer.valueOf(split[1]));
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithDuration {
private Duration duration;
public Duration getDuration() {
return this.duration;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
}
@ConfigurationProperties(prefix = "test")
@Validated
public static class PropertyWithoutJSR303 implements Validator {
private String foo;
@Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(getClass());
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1");
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
@ConfigurationProperties(prefix = "test")
@Validated
public static class PropertyWithJSR303 extends PropertyWithoutJSR303 {
@NotNull
private String bar;
public void setBar(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
}
}

@ -0,0 +1,483 @@
/*
* Copyright 2012-2017 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.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.boot.context.properties.bind.validation.ValidationErrors;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ConfigurationPropertiesBinder}.
*
* @author Stephane Nicoll
*/
public class ConfigurationPropertiesBinderTests {
private final MockEnvironment environment = new MockEnvironment();
@Test
public void bindSimpleProperties() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"person.name=John Smith", "person.age=42");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PersonProperties target = new PersonProperties();
binder.bind(target);
assertThat(target.name).isEqualTo("John Smith");
assertThat(target.age).isEqualTo(42);
}
@Test
public void bindUnknownFieldFailureMessageContainsDetailsOfPropertyOrigin() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"person.does-not-exist=yolo");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PersonProperties target = new PersonProperties();
try {
binder.bind(target);
fail("Expected exception");
}
catch (ConfigurationPropertiesBindingException ex) {
BindException bindException = (BindException) ex.getCause();
assertThat(bindException.getMessage())
.startsWith("Failed to bind properties under 'person' to "
+ PersonProperties.class.getName());
}
}
@Test
public void bindWithIgnoreInvalidFieldsAnnotation() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"com.example.bar=spam");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithIgnoreInvalidFields target = new PropertyWithIgnoreInvalidFields();
binder.bind(target);
assertThat(target.getBar()).isEqualTo(0);
}
@Test
public void bindNonAnnotatedObject() {
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
binder.bind("FooBar");
}
@Test
public void bindToEnum() {
bindToEnum("test.theValue=foo");
}
@Test
public void bindToEnumRelaxed() {
bindToEnum("test.the-value=FoO");
bindToEnum("test.THE_VALUE=FoO");
}
private void bindToEnum(String property) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, property);
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithEnum target = new PropertyWithEnum();
binder.bind(target);
assertThat(target.getTheValue()).isEqualTo(FooEnum.FOO);
}
@Test
public void bindSetOfEnumRelaxed() {
bindToEnumSet("test.the-values=foo,bar", FooEnum.FOO, FooEnum.BAR);
bindToEnumSet("test.the-values=foo", FooEnum.FOO);
}
private void bindToEnumSet(String property, FooEnum... expected) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, property);
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithEnum target = new PropertyWithEnum();
binder.bind(target);
assertThat(target.getTheValues()).contains(expected);
}
@Test
public void bindToCharArray() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.chars=word");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithCharArray target = new PropertyWithCharArray();
binder.bind(target);
assertThat(target.getChars()).isEqualTo("word".toCharArray());
}
@Test
public void bindToRelaxedPropertyNamesSame() throws Exception {
testRelaxedPropertyNames("test.FOO_BAR=test1", "test.FOO_BAR=test2",
"test.BAR-B-A-Z=testa", "test.BAR-B-A-Z=testb");
}
private void testRelaxedPropertyNames(String... pairs) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
pairs);
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithRelaxedNames target = new PropertyWithRelaxedNames();
binder.bind(target);
assertThat(target.getFooBar()).isEqualTo("test2");
assertThat(target.getBarBAZ()).isEqualTo("testb");
}
@Test
public void bindToNestedProperty() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.nested.value=test1");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithNestedValue target = new PropertyWithNestedValue();
binder.bind(target);
assertThat(target.getNested().getValue()).isEqualTo("test1");
}
@Test
public void bindToMap() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.map.foo=bar");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertiesWithMap target = new PropertiesWithMap();
binder.bind(target);
assertThat(target.getMap()).containsOnly(entry("foo", "bar"));
}
@Test
public void bindToMapWithSystemProperties() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new SystemEnvironmentPropertySource("system",
Collections.singletonMap("TEST_MAP_FOO_BAR", "baz")));
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
propertySources, null, null);
PropertiesWithComplexMap target = new PropertiesWithComplexMap();
binder.bind(target);
assertThat(target.getMap()).containsOnlyKeys("foo");
assertThat(target.getMap().get("foo")).containsOnly(entry("bar", "baz"));
}
@Test
public void bindWithOverriddenProperties() {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new SystemEnvironmentPropertySource("system",
Collections.singletonMap("PERSON_NAME", "Jane")));
propertySources.addLast(new MapPropertySource("test",
Collections.singletonMap("person.name", "John")));
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
propertySources, null, null);
PersonProperties target = new PersonProperties();
binder.bind(target);
assertThat(target.name).isEqualTo("Jane");
}
@Test
public void validationWithSetter() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.foo=spam");
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null, null);
PropertyWithValidatingSetter target = new PropertyWithValidatingSetter();
try {
binder.bind(target);
fail("Expected exception");
}
catch (ConfigurationPropertiesBindingException ex) {
BindException bindException = (BindException) ex.getCause();
assertThat(bindException.getMessage())
.startsWith("Failed to bind properties under 'test' to "
+ PropertyWithValidatingSetter.class.getName());
}
}
@Test
public void validationWithCustomValidator() {
CustomPropertyValidator validator = spy(new CustomPropertyValidator());
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null,
validator);
PropertyWithCustomValidator target = new PropertyWithCustomValidator();
assertThat(bindWithValidationErrors(binder, target)
.getAllErrors()).hasSize(1);
verify(validator).validate(eq(target), any(Errors.class));
}
@Test
public void validationWithCustomValidatorNotSupported() {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"test.foo=bar");
CustomPropertyValidator validator = spy(new CustomPropertyValidator());
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
this.environment.getPropertySources(), null,
validator);
PropertyWithValidatingSetter target = new PropertyWithValidatingSetter();
binder.bind(target);
assertThat(target.getFoo()).isEqualTo("bar");
verify(validator, times(0)).validate(eq(target), any(Errors.class));
}
private ValidationErrors bindWithValidationErrors(
ConfigurationPropertiesBinder binder, Object target) {
try {
binder.bind(target);
throw new AssertionError("Should have failed to bind " + target);
}
catch (ConfigurationPropertiesBindingException ex) {
Throwable rootCause = ex.getRootCause();
assertThat(rootCause).isInstanceOf(BindValidationException.class);
return ((BindValidationException) rootCause).getValidationErrors();
}
}
@ConfigurationProperties(value = "person", ignoreUnknownFields = false)
static class PersonProperties {
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
}
@ConfigurationProperties(prefix = "com.example", ignoreInvalidFields = true)
public static class PropertyWithIgnoreInvalidFields {
private long bar;
public void setBar(long bar) {
this.bar = bar;
}
public long getBar() {
return this.bar;
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithEnum {
private FooEnum theValue;
private List<FooEnum> theValues;
public void setTheValue(FooEnum value) {
this.theValue = value;
}
public FooEnum getTheValue() {
return this.theValue;
}
public List<FooEnum> getTheValues() {
return this.theValues;
}
public void setTheValues(List<FooEnum> theValues) {
this.theValues = theValues;
}
}
enum FooEnum {
FOO, BAZ, BAR
}
@ConfigurationProperties(prefix = "test", ignoreUnknownFields = false)
public static class PropertyWithCharArray {
private char[] chars;
public char[] getChars() {
return this.chars;
}
public void setChars(char[] chars) {
this.chars = chars;
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithRelaxedNames {
private String fooBar;
private String barBAZ;
public String getFooBar() {
return this.fooBar;
}
public void setFooBar(String fooBar) {
this.fooBar = fooBar;
}
public String getBarBAZ() {
return this.barBAZ;
}
public void setBarBAZ(String barBAZ) {
this.barBAZ = barBAZ;
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithNestedValue {
private Nested nested = new Nested();
public Nested getNested() {
return this.nested;
}
public static class Nested {
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
}
@Validated
@ConfigurationProperties(prefix = "test")
public static class PropertiesWithMap {
private Map<String, String> map;
public Map<String, String> getMap() {
return this.map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertiesWithComplexMap {
private Map<String, Map<String, String>> map;
public Map<String, Map<String, String>> getMap() {
return this.map;
}
public void setMap(Map<String, Map<String, String>> map) {
this.map = map;
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithValidatingSetter {
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
if (!foo.equals("bar")) {
throw new IllegalArgumentException("Wrong value for foo");
}
}
}
@ConfigurationProperties(prefix = "custom")
@Validated
public static class PropertyWithCustomValidator {
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
public static class CustomPropertyValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return aClass == PropertyWithCustomValidator.class;
}
@Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1");
}
}
}

@ -16,11 +16,9 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -40,7 +38,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.boot.testsupport.rule.OutputCapture;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -60,15 +57,10 @@ import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.fail;
/** /**
* Tests for {@link ConfigurationPropertiesBindingPostProcessor}. * Tests for {@link ConfigurationPropertiesBindingPostProcessor}.
@ -96,78 +88,18 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Test @Test
public void testValidationWithSetter() { public void binderIsNullOutAfterContextRefresh() {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"test.foo=spam");
this.context.register(TestConfigurationWithValidatingSetter.class);
try {
this.context.refresh();
fail("Expected exception");
}
catch (BeanCreationException ex) {
BindException bindException = (BindException) ex.getCause();
assertThat(bindException.getMessage())
.startsWith("Failed to bind properties under 'test' to "
+ PropertyWithValidatingSetter.class.getName());
}
}
@Test
public void unknownFieldFailureMessageContainsDetailsOfPropertyOrigin() {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"com.example.baz=spam");
this.context.register(TestConfiguration.class);
try {
this.context.refresh();
fail("Expected exception");
}
catch (BeanCreationException ex) {
BindException bindException = (BindException) ex.getCause();
assertThat(bindException.getMessage())
.startsWith("Failed to bind properties under 'com.example' to "
+ TestConfiguration.class.getName());
}
}
@Test
public void testValidationWithoutJSR303() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfigurationWithoutJSR303.class);
assertBindingFailure(1);
}
@Test
public void testValidationWithJSR303() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfigurationWithJSR303.class);
assertBindingFailure(2);
}
@Test
public void testValidationAndNullOutValidator() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class); this.context.register(TestConfiguration.class);
this.context.refresh(); this.context.refresh();
ConfigurationPropertiesBindingPostProcessor bean = this.context ConfigurationPropertiesBindingPostProcessor bean = this.context
.getBean(ConfigurationPropertiesBindingPostProcessor.class); .getBean(ConfigurationPropertiesBindingPostProcessor.class);
assertThat(ReflectionTestUtils.getField(bean, "validator")).isNull(); assertThat(ReflectionTestUtils.getField(bean, "configurationPropertiesBinder"))
} .isNull();
@Test
public void testSuccessfulValidationWithJSR303() {
MockEnvironment env = new MockEnvironment();
env.setProperty("test.foo", "123456");
env.setProperty("test.bar", "654321");
this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(env);
this.context.register(TestConfigurationWithJSR303.class);
this.context.refresh();
} }
@Test @Test
public void testSuccessfulValidationWithInterface() { public void bindToInterfaceBean() {
MockEnvironment env = new MockEnvironment(); MockEnvironment env = new MockEnvironment();
env.setProperty("test.foo", "bar"); env.setProperty("test.foo", "bar");
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -179,7 +111,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Test @Test
public void testInitializersSeeBoundProperties() { public void initializerSeeBoundProperties() {
MockEnvironment env = new MockEnvironment(); MockEnvironment env = new MockEnvironment();
env.setProperty("bar", "foo"); env.setProperty("bar", "foo");
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -189,62 +121,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Test @Test
public void testValidationWithCustomValidator() { public void bindWithValueDefault() throws Exception {
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfigurationWithCustomValidator.class);
assertBindingFailure(1);
}
@Test
public void testValidationWithCustomValidatorNotSupported() {
MockEnvironment env = new MockEnvironment();
env.setProperty("test.foo", "bar");
this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(env);
this.context.register(TestConfigurationWithCustomValidator.class,
PropertyWithValidatingSetter.class);
assertBindingFailure(1);
}
@Test
public void testPropertyWithEnum() throws Exception {
doEnumTest("test.theValue=foo");
}
@Test
public void testRelaxedPropertyWithEnum() throws Exception {
doEnumTest("test.the-value=FoO");
doEnumTest("test.THE_VALUE=FoO");
}
private void doEnumTest(String property) {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, property);
this.context.register(PropertyWithEnum.class);
this.context.refresh();
assertThat(this.context.getBean(PropertyWithEnum.class).getTheValue())
.isEqualTo(FooEnum.FOO);
this.context.close();
}
@Test
public void testRelaxedPropertyWithSetOfEnum() {
doEnumSetTest("test.the-values=foo,bar", FooEnum.FOO, FooEnum.BAR);
doEnumSetTest("test.the-values=foo", FooEnum.FOO);
}
private void doEnumSetTest(String property, FooEnum... expected) {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, property);
this.context.register(PropertyWithEnum.class);
this.context.refresh();
assertThat(this.context.getBean(PropertyWithEnum.class).getTheValues())
.contains(expected);
this.context.close();
}
@Test
public void testValueBindingForDefaults() throws Exception {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"default.value=foo"); "default.value=foo");
@ -255,7 +132,7 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Test @Test
public void configurationPropertiesWithFactoryBean() throws Exception { public void binderShouldNotInitializeFactoryBeans() throws Exception {
ConfigurationPropertiesWithFactoryBean.factoryBeanInit = false; ConfigurationPropertiesWithFactoryBean.factoryBeanInit = false;
this.context = new AnnotationConfigApplicationContext() { this.context = new AnnotationConfigApplicationContext() {
@Override @Override
@ -275,57 +152,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
.isTrue(); .isTrue();
} }
@Test
public void configurationPropertiesWithCharArray() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"test.chars=word");
this.context.register(PropertyWithCharArray.class);
this.context.refresh();
assertThat(this.context.getBean(PropertyWithCharArray.class).getChars())
.isEqualTo("word".toCharArray());
}
@Test
public void notWritablePropertyException() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"test.madeup:word");
this.context.register(PropertyWithCharArray.class);
this.thrown.expect(BeanCreationException.class);
this.thrown.expectMessage("test");
this.context.refresh();
}
@Test
public void relaxedPropertyNamesSame() throws Exception {
testRelaxedPropertyNames("test.FOO_BAR=test1", "test.FOO_BAR=test2",
"test.BAR-B-A-Z=testa", "test.BAR-B-A-Z=testb");
}
private void testRelaxedPropertyNames(String... environment) {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
environment);
this.context.register(RelaxedPropertyNames.class);
this.context.refresh();
RelaxedPropertyNames bean = this.context.getBean(RelaxedPropertyNames.class);
assertThat(bean.getFooBar()).isEqualTo("test2");
assertThat(bean.getBarBAZ()).isEqualTo("testb");
}
@Test
public void nestedProperties() throws Exception {
// gh-3539
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"test.nested.value=test1");
this.context.register(PropertyWithNestedValue.class);
this.context.refresh();
assertThat(this.context.getBean(PropertyWithNestedValue.class).getNested()
.getValue()).isEqualTo("test1");
}
@Test @Test
public void bindWithoutConfigurationPropertiesAnnotation() { public void bindWithoutConfigurationPropertiesAnnotation() {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -338,27 +164,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
this.context.refresh(); this.context.refresh();
} }
@Test
public void bindWithIgnoreInvalidFieldsAnnotation() {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"com.example.bar=spam");
this.context.register(TestConfigurationWithIgnoreErrors.class);
this.context.refresh();
assertThat(this.context.getBean(TestConfigurationWithIgnoreErrors.class).getBar())
.isEqualTo(0);
}
@Test
public void bindWithNoIgnoreInvalidFieldsAnnotation() {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"com.example.foo=hello");
this.context.register(TestConfiguration.class);
this.thrown.expect(BeanCreationException.class);
this.context.refresh();
}
@Test @Test
public void multiplePropertySourcesPlaceholderConfigurer() throws Exception { public void multiplePropertySourcesPlaceholderConfigurer() throws Exception {
this.context = new AnnotationConfigApplicationContext(); this.context = new AnnotationConfigApplicationContext();
@ -368,48 +173,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
"Multiple PropertySourcesPlaceholderConfigurer beans registered"); "Multiple PropertySourcesPlaceholderConfigurer beans registered");
} }
@Test
public void propertiesWithMap() throws Exception {
this.context = new AnnotationConfigApplicationContext();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
"test.map.foo=bar");
this.context.register(PropertiesWithMap.class);
this.context.refresh();
assertThat(this.context.getBean(PropertiesWithMap.class).getMap())
.containsEntry("foo", "bar");
}
@Test
public void systemPropertiesShouldBindToMap() throws Exception {
MockEnvironment env = new MockEnvironment();
MutablePropertySources propertySources = env.getPropertySources();
propertySources.addLast(new SystemEnvironmentPropertySource("system",
Collections.singletonMap("TEST_MAP_FOO_BAR", "baz")));
this.context = new AnnotationConfigApplicationContext();
this.context.setEnvironment(env);
this.context.register(PropertiesWithComplexMap.class);
this.context.refresh();
Map<String, Map<String, String>> map = this.context
.getBean(PropertiesWithComplexMap.class).getMap();
Map<String, String> foo = map.get("foo");
assertThat(foo).containsEntry("bar", "baz");
}
@Test
public void overridingPropertiesInEnvShouldOverride() throws Exception {
this.context = new AnnotationConfigApplicationContext();
ConfigurableEnvironment env = this.context.getEnvironment();
MutablePropertySources propertySources = env.getPropertySources();
propertySources.addFirst(new SystemEnvironmentPropertySource("system",
Collections.singletonMap("COM_EXAMPLE_FOO", "10")));
propertySources.addLast(new MapPropertySource("test",
Collections.singletonMap("com.example.foo", 5)));
this.context.register(TestConfiguration.class);
this.context.refresh();
int foo = this.context.getBean(TestConfiguration.class).getFoo();
assertThat(foo).isEqualTo(10);
}
@Test @Test
public void overridingPropertiesWithPlaceholderResolutionInEnvShouldOverride() public void overridingPropertiesWithPlaceholderResolutionInEnvShouldOverride()
throws Exception { throws Exception {
@ -466,19 +229,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
assertThat(second.getTwo()).isEqualTo("baz"); assertThat(second.getTwo()).isEqualTo("baz");
} }
@Test
public void javaTimeDurationCanBeBound() throws Exception {
this.context = new AnnotationConfigApplicationContext();
MutablePropertySources sources = this.context.getEnvironment()
.getPropertySources();
sources.addFirst(new MapPropertySource("test",
Collections.singletonMap("test.duration", "PT1M")));
this.context.register(DurationProperty.class);
this.context.refresh();
Duration duration = this.context.getBean(DurationProperty.class).getDuration();
assertThat(duration.getSeconds()).isEqualTo(60);
}
@Test @Test
public void converterIsFound() { public void converterIsFound() {
prepareConverterContext(ConverterConfiguration.class, PersonProperty.class); prepareConverterContext(ConverterConfiguration.class, PersonProperty.class);
@ -524,94 +274,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
this.context.register(config); this.context.register(config);
} }
private void assertBindingFailure(int errorCount) {
try {
this.context.refresh();
fail("Expected exception");
}
catch (BeanCreationException ex) {
assertThat(((BindValidationException) ex.getRootCause()).getValidationErrors()
.getAllErrors().size()).isEqualTo(errorCount);
}
}
@Configuration
@EnableConfigurationProperties
public static class TestConfigurationWithValidatingSetter {
@Bean
public PropertyWithValidatingSetter testProperties() {
return new PropertyWithValidatingSetter();
}
}
@ConfigurationProperties(prefix = "test")
public static class PropertyWithValidatingSetter {
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
if (!foo.equals("bar")) {
throw new IllegalArgumentException("Wrong value for foo");
}
}
}
@Configuration
@EnableConfigurationProperties
public static class TestConfigurationWithoutJSR303 {
@Bean
public PropertyWithoutJSR303 testProperties() {
return new PropertyWithoutJSR303();
}
}
@ConfigurationProperties(prefix = "test")
@Validated
public static class PropertyWithoutJSR303 implements Validator {
private String foo;
@Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(getClass());
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1");
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
@Configuration
@EnableConfigurationProperties
public static class TestConfigurationWithJSR303 {
@Bean
public PropertyWithJSR303 testProperties() {
return new PropertyWithJSR303();
}
}
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
@ConfigurationProperties @ConfigurationProperties
@ -660,40 +322,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
} }
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "com.example", ignoreInvalidFields = true)
public static class TestConfigurationWithIgnoreErrors {
private long bar;
public void setBar(long bar) {
this.bar = bar;
}
public long getBar() {
return this.bar;
}
}
@ConfigurationProperties(prefix = "test")
@Validated
public static class PropertyWithJSR303 extends PropertyWithoutJSR303 {
@NotNull
private String bar;
public void setBar(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
}
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
public static class TestConfigurationWithValidationAndInterface { public static class TestConfigurationWithValidationAndInterface {
@ -728,118 +356,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Configuration
@EnableConfigurationProperties
public static class TestConfigurationWithCustomValidator {
@Bean
public PropertyWithCustomValidator propertyWithCustomValidator() {
return new PropertyWithCustomValidator();
}
@Bean
public Validator configurationPropertiesValidator() {
return new CustomPropertyValidator();
}
}
@ConfigurationProperties(prefix = "custom")
@Validated
public static class PropertyWithCustomValidator {
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
public static class CustomPropertyValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return aClass == PropertyWithCustomValidator.class;
}
@Override
public void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "foo", "TEST1");
}
}
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test", ignoreUnknownFields = false)
public static class PropertyWithCharArray {
private char[] chars;
public char[] getChars() {
return this.chars;
}
public void setChars(char[] chars) {
this.chars = chars;
}
}
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test", ignoreUnknownFields = false)
public static class PropertyWithCharArrayExpansion {
private char[] chars = new char[] { 'w', 'o', 'r', 'd' };
public char[] getChars() {
return this.chars;
}
public void setChars(char[] chars) {
this.chars = chars;
}
}
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test")
public static class PropertyWithEnum {
private FooEnum theValue;
private List<FooEnum> theValues;
public void setTheValue(FooEnum value) {
this.theValue = value;
}
public FooEnum getTheValue() {
return this.theValue;
}
public List<FooEnum> getTheValues() {
return this.theValues;
}
public void setTheValues(List<FooEnum> theValues) {
this.theValues = theValues;
}
}
enum FooEnum {
FOO, BAZ, BAR
}
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
@ -865,45 +381,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Configuration
@EnableConfigurationProperties
@Validated
@ConfigurationProperties(prefix = "test")
public static class PropertiesWithMap {
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
private Map<String, String> map;
public Map<String, String> getMap() {
return this.map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test")
public static class PropertiesWithComplexMap {
private Map<String, Map<String, String>> map;
public Map<String, Map<String, String>> getMap() {
return this.map;
}
public void setMap(Map<String, Map<String, String>> map) {
this.map = map;
}
}
@Configuration @Configuration
@EnableConfigurationProperties @EnableConfigurationProperties
@ -950,32 +427,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test")
public static class RelaxedPropertyNames {
private String fooBar;
private String barBAZ;
public String getFooBar() {
return this.fooBar;
}
public void setFooBar(String fooBar) {
this.fooBar = fooBar;
}
public String getBarBAZ() {
return this.barBAZ;
}
public void setBarBAZ(String barBAZ) {
this.barBAZ = barBAZ;
}
}
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
// Must be a raw type // Must be a raw type
@ -1003,55 +454,6 @@ public class ConfigurationPropertiesBindingPostProcessorTests {
} }
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test")
public static class PropertyWithNestedValue {
private Nested nested = new Nested();
public Nested getNested() {
return this.nested;
}
@Bean
public static PropertySourcesPlaceholderConfigurer configurer() {
return new PropertySourcesPlaceholderConfigurer();
}
public static class Nested {
@Value("${default.value}")
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}
}
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "test")
public static class DurationProperty {
private Duration duration;
public Duration getDuration() {
return this.duration;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
}
@Configuration @Configuration
@EnableConfigurationProperties(PropertyWithoutConfigurationPropertiesAnnotation.class) @EnableConfigurationProperties(PropertyWithoutConfigurationPropertiesAnnotation.class)

Loading…
Cancel
Save