mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-05-03 03:43:54 +01:00
Validate @ConfigurationProperties on @Bean methods
Refactor `ConfigurationPropertiesBindingPostProcessor` to allow JSR-303 validation on `@ConfigurationProperties` defined at the `@Bean` method level. JSR-303 validation is now applied when a JSR-303 implementation is available and `@Validated` is present on either the configuration properties class itself or the `@Bean` method that creates it. Standard Spring validation is also supported using a validator bean named `configurationPropertiesValidator`, or by having the configuration properties implement `Validator`. The commit also consolidates tests into a single location. Fixes gh-10803
This commit is contained in:
@@ -1213,9 +1213,13 @@ to your fields, as shown in the following example:
|
||||
}
|
||||
----
|
||||
|
||||
In order to validate the values of nested properties, you must annotate the associated
|
||||
field as `@Valid` to trigger its validation. The following example builds on the
|
||||
preceding `AcmeProperties` example:
|
||||
TIP: You can also trigger validation by annotating the `@Bean` method that creates the
|
||||
configuration properties with `@Validated`.
|
||||
|
||||
Although nested properties will also be validated when bound, it's good practice to
|
||||
also annotate the associated field as `@Valid`. This ensure that validation is triggered
|
||||
even if no nested properties are found. The following example builds on the preceding
|
||||
`AcmeProperties` example:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
|
||||
+6
@@ -39,6 +39,12 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {
|
||||
|
||||
/**
|
||||
* The bean name that this class is registered with.
|
||||
*/
|
||||
public static final String BEAN_NAME = ConfigurationBeanFactoryMetadata.class
|
||||
.getName();
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.beans.factory.BeanCreationException;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Exception thrown when {@link ConfigurationProperties @ConfigurationProperties} binding
|
||||
* fails.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ConfigurationPropertiesBindException extends BeanCreationException {
|
||||
|
||||
private final Class<?> beanType;
|
||||
|
||||
private final ConfigurationProperties annotation;
|
||||
|
||||
ConfigurationPropertiesBindException(String beanName, Object bean,
|
||||
ConfigurationProperties annotation, Exception cause) {
|
||||
super(beanName, getMessage(bean, annotation), cause);
|
||||
this.beanType = bean.getClass();
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean type that was being bound.
|
||||
* @return the bean type
|
||||
*/
|
||||
public Class<?> getBeanType() {
|
||||
return this.beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration properties annotation that triggered the binding.
|
||||
* @return the configuration properties annotation
|
||||
*/
|
||||
public ConfigurationProperties getAnnotation() {
|
||||
return this.annotation;
|
||||
}
|
||||
|
||||
private static String getMessage(Object bean, ConfigurationProperties annotation) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append("Could not bind properties to '"
|
||||
+ ClassUtils.getShortName(bean.getClass()) + "' : ");
|
||||
message.append("prefix=").append(annotation.prefix());
|
||||
message.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
|
||||
message.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+62
-100
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
@@ -23,86 +26,78 @@ import org.springframework.boot.context.properties.bind.PropertySourcesPlacehold
|
||||
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.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Bind {@link ConfigurationProperties} annotated object from a configurable list of
|
||||
* {@link PropertySource}.
|
||||
* Internal class by the {@link ConfigurationPropertiesBindingPostProcessor} to handle the
|
||||
* actual {@link ConfigurationProperties} binding.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConfigurationPropertiesBinder {
|
||||
|
||||
private final Iterable<PropertySource<?>> propertySources;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
private final PropertySources propertySources;
|
||||
|
||||
private final Validator validator;
|
||||
private final Validator configurationPropertiesValidator;
|
||||
|
||||
private Iterable<ConfigurationPropertySource> configurationSources;
|
||||
private final Validator jsr303Validator;
|
||||
|
||||
private final Binder binder;
|
||||
|
||||
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;
|
||||
this.configurationSources = ConfigurationPropertySources.from(propertySources);
|
||||
this.binder = new Binder(this.configurationSources,
|
||||
new PropertySourcesPlaceholdersResolver(this.propertySources),
|
||||
this.conversionService);
|
||||
private volatile Binder binder;
|
||||
|
||||
ConfigurationPropertiesBinder(ApplicationContext applicationContext,
|
||||
String validatorBeanName) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.propertySources = new PropertySourcesDeducer(applicationContext)
|
||||
.getPropertySources();
|
||||
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(
|
||||
applicationContext, validatorBeanName);
|
||||
this.jsr303Validator = ConfigurationPropertiesJsr303Validator
|
||||
.getIfJsr303Present(applicationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param targetType the resolvable type for the target
|
||||
* @throws ConfigurationPropertiesBindingException if the binding failed
|
||||
*/
|
||||
void bind(Object target, ConfigurationProperties annotation,
|
||||
ResolvableType targetType) {
|
||||
Validator validator = determineValidator(target);
|
||||
BindHandler handler = getBindHandler(annotation, validator);
|
||||
Bindable<?> bindable = Bindable.of(targetType).withExistingValue(target);
|
||||
try {
|
||||
this.binder.bind(annotation.prefix(), bindable, handler);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
String message = "Could not bind properties to '"
|
||||
+ ClassUtils.getShortName(target.getClass()) + "': "
|
||||
+ getAnnotationDetails(annotation);
|
||||
throw new ConfigurationPropertiesBindingException(message, ex);
|
||||
}
|
||||
public void bind(Bindable<?> target) {
|
||||
ConfigurationProperties annotation = target
|
||||
.getAnnotation(ConfigurationProperties.class);
|
||||
Assert.state(annotation != null, "Missing @ConfigurationProperties on " + target);
|
||||
List<Validator> validators = getValidators(target);
|
||||
BindHandler bindHandler = getBindHandler(annotation, validators);
|
||||
getBinder().bind(annotation.prefix(), target, bindHandler);
|
||||
}
|
||||
|
||||
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;
|
||||
private Validator getConfigurationPropertiesValidator(
|
||||
ApplicationContext applicationContext, String validatorBeanName) {
|
||||
if (applicationContext.containsBean(validatorBeanName)) {
|
||||
return applicationContext.getBean(validatorBeanName, Validator.class);
|
||||
}
|
||||
return (supportsBean ? this.validator : null);
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Validator> getValidators(Bindable<?> target) {
|
||||
List<Validator> validators = new ArrayList<>(3);
|
||||
if (this.configurationPropertiesValidator != null) {
|
||||
validators.add(this.configurationPropertiesValidator);
|
||||
}
|
||||
if (this.jsr303Validator != null
|
||||
&& target.getAnnotation(Validated.class) != null) {
|
||||
validators.add(this.jsr303Validator);
|
||||
}
|
||||
if (target.getValue() != null && target.getValue().get() instanceof Validator) {
|
||||
validators.add((Validator) target.getValue().get());
|
||||
}
|
||||
return validators;
|
||||
}
|
||||
|
||||
private BindHandler getBindHandler(ConfigurationProperties annotation,
|
||||
Validator validator) {
|
||||
List<Validator> validators) {
|
||||
BindHandler handler = BindHandler.DEFAULT;
|
||||
if (annotation.ignoreInvalidFields()) {
|
||||
handler = new IgnoreErrorsBindHandler(handler);
|
||||
@@ -111,55 +106,22 @@ class ConfigurationPropertiesBinder {
|
||||
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
|
||||
handler = new NoUnboundElementsBindHandler(handler, filter);
|
||||
}
|
||||
if (validator != null) {
|
||||
handler = new ValidationBindHandler(handler, validator);
|
||||
if (!validators.isEmpty()) {
|
||||
handler = new ValidationBindHandler(handler,
|
||||
validators.toArray(new Validator[validators.size()]));
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private String getAnnotationDetails(ConfigurationProperties annotation) {
|
||||
if (annotation == null) {
|
||||
return "";
|
||||
private Binder getBinder() {
|
||||
if (this.binder == null) {
|
||||
this.binder = new Binder(
|
||||
ConfigurationPropertySources.from(this.propertySources),
|
||||
new PropertySourcesPlaceholdersResolver(this.propertySources),
|
||||
new ConversionServiceDeducer(this.applicationContext)
|
||||
.getConversionService());
|
||||
}
|
||||
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} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.binder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.PropertySource;
|
||||
import org.springframework.util.Assert;
|
||||
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
|
||||
*/
|
||||
class ConfigurationPropertiesBinderBuilder {
|
||||
|
||||
/**
|
||||
* The bean name of the configuration properties validator.
|
||||
*/
|
||||
static final String VALIDATOR_BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.VALIDATOR_BEAN_NAME;
|
||||
|
||||
/**
|
||||
* The bean name of the configuration properties conversion service.
|
||||
*/
|
||||
static final String CONVERSION_SERVICE_BEAN_NAME = ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME;
|
||||
|
||||
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
|
||||
"javax.validation.ValidatorFactory",
|
||||
"javax.validation.bootstrap.GenericBootstrap" };
|
||||
|
||||
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
|
||||
*/
|
||||
ConfigurationPropertiesBinderBuilder(ApplicationContext applicationContext) {
|
||||
Assert.notNull(applicationContext, "ApplicationContext must not be null");
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the {@link PropertySource property sources} to use.
|
||||
* @param propertySources the configuration the binder should use
|
||||
* @return this instance
|
||||
*/
|
||||
ConfigurationPropertiesBinderBuilder withPropertySources(
|
||||
Iterable<PropertySource<?>> propertySources) {
|
||||
this.propertySources = propertySources;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link ConfigurationPropertiesBinder} based on the state of the builder,
|
||||
* discovering the {@link ConversionService} and {@link Validator} if necessary.
|
||||
* @return a {@link ConfigurationPropertiesBinder}
|
||||
*/
|
||||
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 Jsr303ConfigurationPropertiesValidator(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Exception thrown when a {@code @ConfigurationProperties} annotated object failed to be
|
||||
* bound.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ConfigurationPropertiesBindingException extends RuntimeException {
|
||||
|
||||
ConfigurationPropertiesBindingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
+53
-130
@@ -16,32 +16,21 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
|
||||
@@ -53,156 +42,90 @@ import org.springframework.core.env.StandardEnvironment;
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ConfigurationPropertiesBindingPostProcessor
|
||||
implements BeanPostProcessor, BeanFactoryAware, EnvironmentAware,
|
||||
ApplicationContextAware, InitializingBean, PriorityOrdered {
|
||||
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
|
||||
PriorityOrdered, ApplicationContextAware, InitializingBean {
|
||||
|
||||
/**
|
||||
* The bean name that this post-processor is registered with.
|
||||
*/
|
||||
public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class
|
||||
.getName();
|
||||
|
||||
/**
|
||||
* The bean name of the configuration properties validator.
|
||||
*/
|
||||
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
|
||||
|
||||
private static final Log logger = LogFactory
|
||||
.getLog(ConfigurationPropertiesBindingPostProcessor.class);
|
||||
|
||||
private ConfigurationBeanFactoryMetadata beans = new ConfigurationBeanFactoryMetadata();
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private Environment environment = new StandardEnvironment();
|
||||
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private ConfigurationPropertiesBinder configurationPropertiesBinder;
|
||||
|
||||
private PropertySources propertySources;
|
||||
|
||||
/**
|
||||
* Return the order of the bean.
|
||||
* @return the order
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean meta-data store.
|
||||
* @param beans the bean meta data store
|
||||
*/
|
||||
public void setBeanMetadataStore(ConfigurationBeanFactoryMetadata beans) {
|
||||
this.beans = beans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.propertySources = deducePropertySources();
|
||||
// We can't use constructor injection of the application context because
|
||||
// it causes eager factory bean initialization
|
||||
this.beanFactoryMetadata = this.applicationContext.getBean(
|
||||
ConfigurationBeanFactoryMetadata.BEAN_NAME,
|
||||
ConfigurationBeanFactoryMetadata.class);
|
||||
this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
|
||||
this.applicationContext, VALIDATOR_BEAN_NAME);
|
||||
}
|
||||
|
||||
private PropertySources deducePropertySources() {
|
||||
MutablePropertySources environmentPropertySources = extractEnvironmentPropertySources();
|
||||
PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer();
|
||||
if (configurer == null) {
|
||||
if (environmentPropertySources != null) {
|
||||
return environmentPropertySources;
|
||||
}
|
||||
throw new IllegalStateException("Unable to obtain PropertySources from "
|
||||
+ "PropertySourcesPlaceholderConfigurer or Environment");
|
||||
}
|
||||
PropertySources appliedPropertySources = configurer.getAppliedPropertySources();
|
||||
if (environmentPropertySources == null) {
|
||||
return appliedPropertySources;
|
||||
}
|
||||
return new CompositePropertySources(new FilteredPropertySources(
|
||||
appliedPropertySources,
|
||||
PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME),
|
||||
environmentPropertySources);
|
||||
}
|
||||
|
||||
private MutablePropertySources extractEnvironmentPropertySources() {
|
||||
if (this.environment instanceof ConfigurableEnvironment) {
|
||||
return ((ConfigurableEnvironment) this.environment).getPropertySources();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
|
||||
// Take care not to cause early instantiation of all FactoryBeans
|
||||
if (this.beanFactory instanceof ListableBeanFactory) {
|
||||
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
|
||||
Map<String, PropertySourcesPlaceholderConfigurer> beans = listableBeanFactory
|
||||
.getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false,
|
||||
false);
|
||||
if (beans.size() == 1) {
|
||||
return beans.values().iterator().next();
|
||||
}
|
||||
if (beans.size() > 1 && logger.isWarnEnabled()) {
|
||||
logger.warn("Multiple PropertySourcesPlaceholderConfigurer "
|
||||
+ "beans registered " + beans.keySet()
|
||||
+ ", falling back to Environment");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
ConfigurationProperties annotation = getAnnotation(bean, beanName);
|
||||
ConfigurationProperties annotation = getAnnotation(bean, beanName,
|
||||
ConfigurationProperties.class);
|
||||
if (annotation != null) {
|
||||
try {
|
||||
ResolvableType type = ResolvableType.forClass(bean.getClass());
|
||||
Method factoryMethod = this.beans.findFactoryMethod(beanName);
|
||||
if (factoryMethod != null) {
|
||||
type = ResolvableType.forMethodReturnType(factoryMethod);
|
||||
}
|
||||
getBinder().bind(bean, annotation, type);
|
||||
}
|
||||
catch (ConfigurationPropertiesBindingException ex) {
|
||||
throw new BeanCreationException(beanName, ex.getMessage(), ex.getCause());
|
||||
}
|
||||
bind(bean, beanName, annotation);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName)
|
||||
throws BeansException {
|
||||
return bean;
|
||||
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
|
||||
ResolvableType type = getBeanType(bean, beanName);
|
||||
Validated validated = getAnnotation(bean, beanName, Validated.class);
|
||||
Annotation[] annotations = (validated == null ? new Annotation[] { annotation }
|
||||
: new Annotation[] { annotation, validated });
|
||||
Bindable<?> target = Bindable.of(type).withExistingValue(bean)
|
||||
.withAnnotations(annotations);
|
||||
try {
|
||||
this.configurationPropertiesBinder.bind(target);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigurationProperties getAnnotation(Object bean, String beanName) {
|
||||
ConfigurationProperties annotation = this.beans.findFactoryAnnotation(beanName,
|
||||
ConfigurationProperties.class);
|
||||
private ResolvableType getBeanType(Object bean, String beanName) {
|
||||
Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
|
||||
if (factoryMethod != null) {
|
||||
return ResolvableType.forMethodReturnType(factoryMethod);
|
||||
}
|
||||
return ResolvableType.forClass(bean.getClass());
|
||||
}
|
||||
|
||||
private <A extends Annotation> A getAnnotation(Object bean, String beanName,
|
||||
Class<A> type) {
|
||||
A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
|
||||
if (annotation == null) {
|
||||
annotation = AnnotationUtils.findAnnotation(bean.getClass(),
|
||||
ConfigurationProperties.class);
|
||||
annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
|
||||
}
|
||||
return annotation;
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBinder getBinder() {
|
||||
if (this.configurationPropertiesBinder == null) {
|
||||
this.configurationPropertiesBinder = new ConfigurationPropertiesBinderBuilder(
|
||||
this.applicationContext).withPropertySources(this.propertySources)
|
||||
.build();
|
||||
}
|
||||
return this.configurationPropertiesBinder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+25
-17
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
@@ -31,26 +32,33 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
public class ConfigurationPropertiesBindingPostProcessorRegistrar
|
||||
implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* The bean name of the {@link ConfigurationPropertiesBindingPostProcessor}.
|
||||
*/
|
||||
public static final String BINDER_BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class
|
||||
.getName();
|
||||
|
||||
private static final String METADATA_BEAN_NAME = BINDER_BEAN_NAME + ".store";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(BINDER_BEAN_NAME)) {
|
||||
BeanDefinitionBuilder meta = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ConfigurationBeanFactoryMetadata.class);
|
||||
BeanDefinitionBuilder bean = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
ConfigurationPropertiesBindingPostProcessor.class);
|
||||
bean.addPropertyReference("beanMetadataStore", METADATA_BEAN_NAME);
|
||||
registry.registerBeanDefinition(BINDER_BEAN_NAME, bean.getBeanDefinition());
|
||||
registry.registerBeanDefinition(METADATA_BEAN_NAME, meta.getBeanDefinition());
|
||||
if (!registry.containsBeanDefinition(
|
||||
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
|
||||
registerConfigurationPropertiesBindingPostProcessor(registry);
|
||||
registerConfigurationBeanFactoryMetadata(registry);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerConfigurationPropertiesBindingPostProcessor(
|
||||
BeanDefinitionRegistry registry) {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(
|
||||
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
|
||||
|
||||
}
|
||||
|
||||
private void registerConfigurationBeanFactoryMetadata(
|
||||
BeanDefinitionRegistry registry) {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME,
|
||||
definition);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-5
@@ -18,7 +18,7 @@ package org.springframework.boot.context.properties;
|
||||
|
||||
import org.springframework.boot.validation.MessageInterpolatorFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -30,18 +30,22 @@ import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class Jsr303ConfigurationPropertiesValidator implements Validator {
|
||||
final class ConfigurationPropertiesJsr303Validator implements Validator {
|
||||
|
||||
private static final String[] VALIDATOR_CLASSES = { "javax.validation.Validator",
|
||||
"javax.validation.ValidatorFactory",
|
||||
"javax.validation.bootstrap.GenericBootstrap" };
|
||||
|
||||
private final Delegate delegate;
|
||||
|
||||
Jsr303ConfigurationPropertiesValidator(ApplicationContext applicationContext) {
|
||||
private ConfigurationPropertiesJsr303Validator(
|
||||
ApplicationContext applicationContext) {
|
||||
this.delegate = new Delegate(applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> type) {
|
||||
return AnnotatedElementUtils.hasAnnotation(type, Validated.class)
|
||||
&& this.delegate.supports(type);
|
||||
return this.delegate.supports(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,6 +53,17 @@ class Jsr303ConfigurationPropertiesValidator implements Validator {
|
||||
this.delegate.validate(target, errors);
|
||||
}
|
||||
|
||||
public static ConfigurationPropertiesJsr303Validator getIfJsr303Present(
|
||||
ApplicationContext applicationContext) {
|
||||
ClassLoader classLoader = applicationContext.getClassLoader();
|
||||
for (String validatorClass : VALIDATOR_CLASSES) {
|
||||
if (!ClassUtils.isPresent(validatorClass, classLoader)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new ConfigurationPropertiesJsr303Validator(applicationContext);
|
||||
}
|
||||
|
||||
private static class Delegate extends LocalValidatorFactoryBean {
|
||||
|
||||
Delegate(ApplicationContext applicationContext) {
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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;
|
||||
|
||||
/**
|
||||
* Utility to deduce the {@link ConversionService} to use for configuration properties
|
||||
* binding.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConversionServiceDeducer {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
ConversionServiceDeducer(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public ConversionService getConversionService() {
|
||||
try {
|
||||
return this.applicationContext.getBean(
|
||||
ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
|
||||
ConversionService.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return this.applicationContext.getAutowireCapableBeanFactory()
|
||||
.createBean(Factory.class).create();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Factory {
|
||||
|
||||
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 create() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
for (Converter<?, ?> converter : this.converters) {
|
||||
conversionService.addConverter(converter);
|
||||
}
|
||||
for (GenericConverter genericConverter : this.genericConverters) {
|
||||
conversionService.addConverter(genericConverter);
|
||||
}
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+54
-63
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 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.
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package org.springframework.boot.context.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -47,19 +49,13 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
|
||||
|
||||
private static final String[] IMPORTS = {
|
||||
ConfigurationPropertiesBeanRegistrar.class.getName(),
|
||||
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
|
||||
|
||||
@Override
|
||||
public String[] selectImports(AnnotationMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||
EnableConfigurationProperties.class.getName(), false);
|
||||
Object[] type = attributes == null ? null
|
||||
: (Object[]) attributes.getFirst("value");
|
||||
if (type == null || type.length == 0) {
|
||||
return new String[] {
|
||||
ConfigurationPropertiesBindingPostProcessorRegistrar.class
|
||||
.getName() };
|
||||
}
|
||||
return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
|
||||
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
|
||||
return IMPORTS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,73 +67,68 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
getTypes(metadata).forEach((type) -> register(registry,
|
||||
(ConfigurableListableBeanFactory) registry, type));
|
||||
}
|
||||
|
||||
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata
|
||||
.getAllAnnotationAttributes(
|
||||
EnableConfigurationProperties.class.getName(), false);
|
||||
List<Class<?>> types = collectClasses(attributes.get("value"));
|
||||
for (Class<?> type : types) {
|
||||
String prefix = extractPrefix(type);
|
||||
String name = (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
|
||||
: type.getName());
|
||||
if (!containsBeanDefinition((ConfigurableListableBeanFactory) registry,
|
||||
name)) {
|
||||
registerBeanDefinition(registry, type, name);
|
||||
}
|
||||
return collectClasses(attributes == null ? Collections.emptyList()
|
||||
: attributes.get("value"));
|
||||
}
|
||||
|
||||
private List<Class<?>> collectClasses(List<?> values) {
|
||||
return values.stream().flatMap((value) -> Arrays.stream((Object[]) value))
|
||||
.map((o) -> (Class<?>) o).filter((type) -> void.class != type)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void register(BeanDefinitionRegistry registry,
|
||||
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
|
||||
String name = getName(type);
|
||||
if (!containsBeanDefinition(beanFactory, name)) {
|
||||
registerBeanDefinition(registry, name, type);
|
||||
}
|
||||
}
|
||||
|
||||
private String extractPrefix(Class<?> type) {
|
||||
private String getName(Class<?> type) {
|
||||
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type,
|
||||
ConfigurationProperties.class);
|
||||
if (annotation != null) {
|
||||
return annotation.prefix();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private List<Class<?>> collectClasses(List<Object> list) {
|
||||
ArrayList<Class<?>> result = new ArrayList<>();
|
||||
for (Object object : list) {
|
||||
for (Object value : (Object[]) object) {
|
||||
if (value instanceof Class && value != void.class) {
|
||||
result.add((Class<?>) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(BeanDefinitionRegistry registry,
|
||||
Class<?> type, String name) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(type);
|
||||
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
|
||||
registry.registerBeanDefinition(name, beanDefinition);
|
||||
|
||||
ConfigurationProperties properties = AnnotationUtils.findAnnotation(type,
|
||||
ConfigurationProperties.class);
|
||||
Assert.notNull(properties,
|
||||
"No " + ConfigurationProperties.class.getSimpleName()
|
||||
+ " annotation found on '" + type.getName() + "'.");
|
||||
String prefix = (annotation != null ? annotation.prefix() : "");
|
||||
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName()
|
||||
: type.getName());
|
||||
}
|
||||
|
||||
private boolean containsBeanDefinition(
|
||||
ConfigurableListableBeanFactory beanFactory, String name) {
|
||||
|
||||
boolean result = beanFactory.containsBeanDefinition(name);
|
||||
if (result) {
|
||||
if (beanFactory.containsBeanDefinition(name)) {
|
||||
return true;
|
||||
}
|
||||
if (beanFactory
|
||||
.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
|
||||
return containsBeanDefinition(
|
||||
(ConfigurableListableBeanFactory) beanFactory
|
||||
.getParentBeanFactory(),
|
||||
BeanFactory parent = beanFactory.getParentBeanFactory();
|
||||
if (parent instanceof ConfigurableListableBeanFactory) {
|
||||
return containsBeanDefinition((ConfigurableListableBeanFactory) parent,
|
||||
name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name,
|
||||
Class<?> type) {
|
||||
assertHasAnnotation(type);
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(type);
|
||||
registry.registerBeanDefinition(name, definition);
|
||||
}
|
||||
|
||||
private void assertHasAnnotation(Class<?> type) {
|
||||
Assert.notNull(
|
||||
AnnotationUtils.findAnnotation(type, ConfigurationProperties.class),
|
||||
"No " + ConfigurationProperties.class.getSimpleName()
|
||||
+ " annotation found on '" + type.getName() + "'.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to deduce the {@link PropertySources} to use for configuration binding.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class PropertySourcesDeducer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PropertySourcesDeducer.class);
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
PropertySourcesDeducer(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public PropertySources getPropertySources() {
|
||||
MutablePropertySources environmentPropertySources = extractEnvironmentPropertySources();
|
||||
PropertySourcesPlaceholderConfigurer placeholderConfigurer = getSinglePropertySourcesPlaceholderConfigurer();
|
||||
if (placeholderConfigurer == null) {
|
||||
Assert.state(environmentPropertySources != null,
|
||||
"Unable to obtain PropertySources from "
|
||||
+ "PropertySourcesPlaceholderConfigurer or Environment");
|
||||
return environmentPropertySources;
|
||||
}
|
||||
PropertySources appliedPropertySources = placeholderConfigurer
|
||||
.getAppliedPropertySources();
|
||||
if (environmentPropertySources == null) {
|
||||
return appliedPropertySources;
|
||||
}
|
||||
return merge(environmentPropertySources, appliedPropertySources);
|
||||
}
|
||||
|
||||
private MutablePropertySources extractEnvironmentPropertySources() {
|
||||
Environment environment = this.applicationContext.getEnvironment();
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
return ((ConfigurableEnvironment) environment).getPropertySources();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
|
||||
// Take care not to cause early instantiation of all FactoryBeans
|
||||
Map<String, PropertySourcesPlaceholderConfigurer> beans = this.applicationContext
|
||||
.getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false, false);
|
||||
if (beans.size() == 1) {
|
||||
return beans.values().iterator().next();
|
||||
}
|
||||
if (beans.size() > 1 && logger.isWarnEnabled()) {
|
||||
logger.warn(
|
||||
"Multiple PropertySourcesPlaceholderConfigurer " + "beans registered "
|
||||
+ beans.keySet() + ", falling back to Environment");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PropertySources merge(PropertySources environmentPropertySources,
|
||||
PropertySources appliedPropertySources) {
|
||||
FilteredPropertySources filtered = new FilteredPropertySources(
|
||||
appliedPropertySources,
|
||||
PropertySourcesPlaceholderConfigurer.ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME);
|
||||
return new CompositePropertySources(filtered, environmentPropertySources);
|
||||
}
|
||||
|
||||
}
|
||||
+17
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 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.
|
||||
@@ -86,6 +86,22 @@ public final class Bindable<T> {
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single associated annotations that could affect binding.
|
||||
* @param <A> the annotation type
|
||||
* @param type annotation type
|
||||
* @return the associated annotation or {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A extends Annotation> A getAnnotation(Class<A> type) {
|
||||
for (Annotation annotation : this.annotations) {
|
||||
if (type.isInstance(annotation)) {
|
||||
return (A) annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator creator = new ToStringCreator(this);
|
||||
|
||||
+11
@@ -32,6 +32,7 @@ public class BindValidationException extends RuntimeException {
|
||||
private final ValidationErrors validationErrors;
|
||||
|
||||
BindValidationException(ValidationErrors validationErrors) {
|
||||
super(getMessage(validationErrors));
|
||||
Assert.notNull(validationErrors, "ValidationErrors must not be null");
|
||||
this.validationErrors = validationErrors;
|
||||
}
|
||||
@@ -44,4 +45,14 @@ public class BindValidationException extends RuntimeException {
|
||||
return this.validationErrors;
|
||||
}
|
||||
|
||||
private static String getMessage(ValidationErrors errors) {
|
||||
StringBuilder message = new StringBuilder("Binding validation errors");
|
||||
if (errors != null) {
|
||||
message.append(" on " + errors.getName());
|
||||
errors.getAllErrors().forEach(
|
||||
(error) -> message.append(String.format("%n - %s", error)));
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 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.
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.boot.context.properties.bind.validation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -27,11 +29,9 @@ import org.springframework.boot.context.properties.bind.BindHandler;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.validation.BeanPropertyBindingResult;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* {@link BindHandler} to apply {@link Validator Validators} to bound results.
|
||||
@@ -44,10 +44,10 @@ public class ValidationBindHandler extends AbstractBindHandler {
|
||||
|
||||
private final Validator[] validators;
|
||||
|
||||
private boolean validate;
|
||||
|
||||
private final Set<ConfigurationProperty> boundProperties = new LinkedHashSet<>();
|
||||
|
||||
private final Deque<BindValidationException> exceptions = new LinkedList<>();
|
||||
|
||||
public ValidationBindHandler(Validator... validators) {
|
||||
this.validators = validators;
|
||||
}
|
||||
@@ -57,21 +57,6 @@ public class ValidationBindHandler extends AbstractBindHandler {
|
||||
this.validators = validators;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStart(ConfigurationPropertyName name, Bindable<?> target,
|
||||
BindContext context) {
|
||||
if (context.getDepth() == 0) {
|
||||
this.validate = shouldValidate(target);
|
||||
}
|
||||
return super.onStart(name, target, context);
|
||||
}
|
||||
|
||||
private boolean shouldValidate(Bindable<?> target) {
|
||||
Validated annotation = AnnotationUtils
|
||||
.findAnnotation(target.getBoxedType().resolve(), Validated.class);
|
||||
return (annotation != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
|
||||
BindContext context, Object result) {
|
||||
@@ -84,8 +69,9 @@ public class ValidationBindHandler extends AbstractBindHandler {
|
||||
@Override
|
||||
public void onFinish(ConfigurationPropertyName name, Bindable<?> target,
|
||||
BindContext context, Object result) throws Exception {
|
||||
if (this.validate) {
|
||||
validate(name, target, result);
|
||||
validate(name, target, result);
|
||||
if (context.getDepth() == 0 && !this.exceptions.isEmpty()) {
|
||||
throw this.exceptions.pop();
|
||||
}
|
||||
super.onFinish(name, target, context, result);
|
||||
}
|
||||
@@ -110,22 +96,22 @@ public class ValidationBindHandler extends AbstractBindHandler {
|
||||
private void validate(ConfigurationPropertyName name, Object target, Class<?> type) {
|
||||
if (target != null) {
|
||||
BindingResult errors = new BeanPropertyBindingResult(target, name.toString());
|
||||
Arrays.stream(this.validators).filter((v) -> v.supports(type))
|
||||
.forEach((v) -> v.validate(target, errors));
|
||||
Arrays.stream(this.validators).filter((validator) -> validator.supports(type))
|
||||
.forEach((validator) -> validator.validate(target, errors));
|
||||
if (errors.hasErrors()) {
|
||||
throwBindValidationException(name, errors);
|
||||
this.exceptions.push(getBindValidationException(name, errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void throwBindValidationException(ConfigurationPropertyName name,
|
||||
BindingResult errors) {
|
||||
private BindValidationException getBindValidationException(
|
||||
ConfigurationPropertyName name, BindingResult errors) {
|
||||
Set<ConfigurationProperty> boundProperties = this.boundProperties.stream()
|
||||
.filter((property) -> name.isAncestorOf(property.getName()))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
ValidationErrors validationErrors = new ValidationErrors(name, boundProperties,
|
||||
errors.getAllErrors());
|
||||
throw new BindValidationException(validationErrors);
|
||||
return new BindValidationException(validationErrors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,11 +22,11 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
@@ -76,7 +76,7 @@ public class ValidationErrors implements Iterable<ObjectError> {
|
||||
|
||||
private FieldError convertFieldError(ConfigurationPropertyName name,
|
||||
Set<ConfigurationProperty> boundProperties, FieldError error) {
|
||||
if (error instanceof ObjectProvider<?>) {
|
||||
if (error instanceof OriginProvider) {
|
||||
return error;
|
||||
}
|
||||
return OriginTrackedFieldError.of(error,
|
||||
|
||||
-195
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.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.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* 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 detectDefaultConversionService() {
|
||||
this.applicationContext.registerSingleton("conversionService",
|
||||
DefaultConversionService.class);
|
||||
ConfigurationPropertiesBinder binder = builderWithSources().build();
|
||||
assertThat(ReflectionTestUtils.getField(binder, "conversionService"))
|
||||
.isSameAs(this.applicationContext.getBean("conversionService"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToJavaTimeDuration() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
|
||||
"test.duration=PT1M");
|
||||
ConfigurationPropertiesBinder binder = builderWithSources().build();
|
||||
PropertyWithDuration target = new PropertyWithDuration();
|
||||
bind(binder, target);
|
||||
assertThat(target.getDuration().getSeconds()).isEqualTo(60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectDefaultValidator() {
|
||||
this.applicationContext.registerSingleton(
|
||||
ConfigurationPropertiesBindingPostProcessor.VALIDATOR_BEAN_NAME,
|
||||
LocalValidatorFactoryBean.class);
|
||||
ConfigurationPropertiesBinder binder = builderWithSources().build();
|
||||
assertThat(ReflectionTestUtils.getField(binder, "validator"))
|
||||
.isSameAs(this.applicationContext.getBean(
|
||||
ConfigurationPropertiesBindingPostProcessor.VALIDATOR_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validationWithoutJsr303() {
|
||||
ConfigurationPropertiesBinder binder = builderWithSources().build();
|
||||
assertThat(bindWithValidationErrors(binder, new PropertyWithoutJSR303())
|
||||
.getAllErrors()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validationWithJsr303() {
|
||||
ConfigurationPropertiesBinder binder = builderWithSources().build();
|
||||
assertThat(
|
||||
bindWithValidationErrors(binder, new PropertyWithJSR303()).getAllErrors())
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBinderBuilder builderWithSources() {
|
||||
return this.builder.withPropertySources(this.environment.getPropertySources());
|
||||
}
|
||||
|
||||
@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();
|
||||
bind(binder, target);
|
||||
assertThat(target.getFoo()).isEqualTo("123456");
|
||||
assertThat(target.getBar()).isEqualTo("654321");
|
||||
}
|
||||
|
||||
private ValidationErrors bindWithValidationErrors(
|
||||
ConfigurationPropertiesBinder binder, Object target) {
|
||||
try {
|
||||
bind(binder, 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();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(ConfigurationPropertiesBinder binder, Object target) {
|
||||
binder.bind(target,
|
||||
AnnotationUtils.findAnnotation(target.getClass(),
|
||||
ConfigurationProperties.class),
|
||||
ResolvableType.forType(target.getClass()));
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-485
@@ -1,485 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
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();
|
||||
bind(binder, 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 {
|
||||
bind(binder, 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();
|
||||
bind(binder, target);
|
||||
assertThat(target.getBar()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@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();
|
||||
bind(binder, 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();
|
||||
bind(binder, 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();
|
||||
bind(binder, target);
|
||||
assertThat(target.getChars()).isEqualTo("word".toCharArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToRelaxedPropertyNamesSame() {
|
||||
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();
|
||||
bind(binder, 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();
|
||||
bind(binder, 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();
|
||||
bind(binder, target);
|
||||
assertThat(target.getMap()).containsOnly(entry("foo", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWithSystemProperties() {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addLast(new SystemEnvironmentPropertySource(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||
Collections.singletonMap("TEST_MAP_FOO_BAR", "baz")));
|
||||
ConfigurationPropertiesBinder binder = new ConfigurationPropertiesBinder(
|
||||
propertySources, null, null);
|
||||
PropertiesWithComplexMap target = new PropertiesWithComplexMap();
|
||||
bind(binder, 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();
|
||||
bind(binder, 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 {
|
||||
bind(binder, 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();
|
||||
bind(binder, target);
|
||||
assertThat(target.getFoo()).isEqualTo("bar");
|
||||
verify(validator, times(0)).validate(eq(target), any(Errors.class));
|
||||
}
|
||||
|
||||
private ValidationErrors bindWithValidationErrors(
|
||||
ConfigurationPropertiesBinder binder, Object target) {
|
||||
try {
|
||||
bind(binder, 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();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(ConfigurationPropertiesBinder binder, Object target) {
|
||||
binder.bind(target,
|
||||
AnnotationUtils.findAnnotation(target.getClass(),
|
||||
ConfigurationProperties.class),
|
||||
ResolvableType.forType(target.getClass()));
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-689
@@ -1,689 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.boot.testsupport.rule.OutputCapture;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.env.SystemEnvironmentPropertySource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.context.support.TestPropertySourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationPropertiesBindingPostProcessor}.
|
||||
*
|
||||
* @author Christian Dupuis
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class ConfigurationPropertiesBindingPostProcessorTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public OutputCapture output = new OutputCapture();
|
||||
|
||||
private AnnotationConfigApplicationContext context;
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToInterfaceBean() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("test.foo", "bar");
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.register(TestConfigurationWithValidationAndInterface.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(ValidatedPropertiesImpl.class).getFoo())
|
||||
.isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializerSeeBoundProperties() {
|
||||
MockEnvironment env = new MockEnvironment();
|
||||
env.setProperty("bar", "foo");
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.setEnvironment(env);
|
||||
this.context.register(TestConfigurationWithInitializer.class);
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWithValueDefault() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"default.value=foo");
|
||||
this.context.register(PropertyWithValue.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(PropertyWithValue.class).getValue())
|
||||
.isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void binderShouldNotInitializeFactoryBeans() {
|
||||
ConfigurationPropertiesWithFactoryBean.factoryBeanInit = false;
|
||||
this.context = new AnnotationConfigApplicationContext() {
|
||||
@Override
|
||||
protected void onRefresh() throws BeansException {
|
||||
assertThat(ConfigurationPropertiesWithFactoryBean.factoryBeanInit)
|
||||
.as("Init too early").isFalse();
|
||||
super.onRefresh();
|
||||
}
|
||||
};
|
||||
this.context.register(ConfigurationPropertiesWithFactoryBean.class);
|
||||
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||
beanDefinition.setBeanClass(FactoryBeanTester.class);
|
||||
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
this.context.registerBeanDefinition("test", beanDefinition);
|
||||
this.context.refresh();
|
||||
assertThat(ConfigurationPropertiesWithFactoryBean.factoryBeanInit).as("No init")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindWithoutConfigurationPropertiesAnnotation() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name:foo");
|
||||
this.context.register(ConfigurationPropertiesWithoutAnnotation.class);
|
||||
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("No ConfigurationProperties annotation found");
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiplePropertySourcesPlaceholderConfigurer() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
this.context.register(MultiplePropertySourcesPlaceholderConfigurer.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.output.toString()).contains(
|
||||
"Multiple PropertySourcesPlaceholderConfigurer beans registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overridingPropertiesWithPlaceholderResolutionInEnvShouldOverride() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
ConfigurableEnvironment env = this.context.getEnvironment();
|
||||
MutablePropertySources propertySources = env.getPropertySources();
|
||||
propertySources.addFirst(new SystemEnvironmentPropertySource("system",
|
||||
Collections.singletonMap("COM_EXAMPLE_BAR", "10")));
|
||||
Map<String, Object> source = new HashMap<>();
|
||||
source.put("com.example.bar", 5);
|
||||
source.put("com.example.foo", "${com.example.bar}");
|
||||
propertySources.addLast(new MapPropertySource("test", source));
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
int foo = this.context.getBean(TestConfiguration.class).getFoo();
|
||||
assertThat(foo).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unboundElementsFromSystemEnvironmentShouldNotThrowException() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
ConfigurableEnvironment env = this.context.getEnvironment();
|
||||
MutablePropertySources propertySources = env.getPropertySources();
|
||||
propertySources.addFirst(new MapPropertySource("test",
|
||||
Collections.singletonMap("com.example.foo", 5)));
|
||||
propertySources.addLast(new SystemEnvironmentPropertySource(
|
||||
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
|
||||
Collections.singletonMap("COM_EXAMPLE_OTHER", "10")));
|
||||
this.context.register(TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
int foo = this.context.getBean(TestConfiguration.class).getFoo();
|
||||
assertThat(foo).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rebindableConfigurationProperties() {
|
||||
// gh-9160
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("example.one", "foo");
|
||||
sources.addFirst(new MapPropertySource("test-source", source));
|
||||
this.context.register(PrototypePropertiesConfig.class);
|
||||
this.context.refresh();
|
||||
PrototypeBean first = this.context.getBean(PrototypeBean.class);
|
||||
assertThat(first.getOne()).isEqualTo("foo");
|
||||
source.put("example.one", "bar");
|
||||
sources.addFirst(new MapPropertySource("extra",
|
||||
Collections.singletonMap("example.two", "baz")));
|
||||
PrototypeBean second = this.context.getBean(PrototypeBean.class);
|
||||
assertThat(second.getOne()).isEqualTo("bar");
|
||||
assertThat(second.getTwo()).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rebindableConfigurationPropertiesWithPropertySourcesPlaceholderConfigurer() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("example.one", "foo");
|
||||
sources.addFirst(new MapPropertySource("test-source", source));
|
||||
this.context.register(PrototypePropertiesConfig.class);
|
||||
this.context.register(PropertySourcesPlaceholderConfigurerConfiguration.class);
|
||||
this.context.refresh();
|
||||
PrototypeBean first = this.context.getBean(PrototypeBean.class);
|
||||
assertThat(first.getOne()).isEqualTo("foo");
|
||||
source.put("example.one", "bar");
|
||||
sources.addFirst(new MapPropertySource("extra",
|
||||
Collections.singletonMap("example.two", "baz")));
|
||||
PrototypeBean second = this.context.getBean(PrototypeBean.class);
|
||||
assertThat(second.getOne()).isEqualTo("bar");
|
||||
assertThat(second.getTwo()).isEqualTo("baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converterIsFound() {
|
||||
prepareConverterContext(ConverterConfiguration.class, PersonProperty.class);
|
||||
this.context.refresh();
|
||||
Person person = this.context.getBean(PersonProperty.class).getPerson();
|
||||
assertThat(person.firstName).isEqualTo("John");
|
||||
assertThat(person.lastName).isEqualTo("Smith");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void converterWithoutQualifierIsNotInvoked() {
|
||||
prepareConverterContext(NonQualifiedConverterConfiguration.class,
|
||||
PersonProperty.class);
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectCause(instanceOf(BindException.class));
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void genericConverterIsFound() {
|
||||
prepareConverterContext(GenericConverterConfiguration.class,
|
||||
PersonProperty.class);
|
||||
this.context.refresh();
|
||||
Person person = this.context.getBean(PersonProperty.class).getPerson();
|
||||
assertThat(person.firstName).isEqualTo("John");
|
||||
assertThat(person.lastName).isEqualTo("Smith");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void genericConverterWithoutQualifierIsNotInvoked() {
|
||||
prepareConverterContext(NonQualifiedGenericConverterConfiguration.class,
|
||||
PersonProperty.class);
|
||||
this.thrown.expect(BeanCreationException.class);
|
||||
this.thrown.expectCause(instanceOf(BindException.class));
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToMapWithNumericKey() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("sample.foos.1.name", "One");
|
||||
sources.addFirst(new MapPropertySource("test-source", source));
|
||||
this.context.register(NumericKeyConfiguration.class);
|
||||
this.context.refresh();
|
||||
NumericKeyConfiguration foo = this.context.getBean(NumericKeyConfiguration.class);
|
||||
assertThat(foo.getFoos().get("1")).isNotNull();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void bindToBeanWithGenerics() {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("foo.bar", "hello");
|
||||
sources.addFirst(new MapPropertySource("test-source", source));
|
||||
this.context.register(GenericConfiguration.class);
|
||||
this.context.refresh();
|
||||
AGenericClass foo = this.context.getBean(AGenericClass.class);
|
||||
assertThat(foo.getBar()).isNotNull();
|
||||
}
|
||||
|
||||
private void prepareConverterContext(Class<?>... config) {
|
||||
this.context = new AnnotationConfigApplicationContext();
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
sources.addFirst(new MapPropertySource("test",
|
||||
Collections.singletonMap("test.person", "John Smith")));
|
||||
this.context.register(config);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties
|
||||
public static class TestConfigurationWithInitializer {
|
||||
|
||||
private String bar;
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
assertThat(this.bar).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "com.example", ignoreUnknownFields = false)
|
||||
public static class TestConfiguration {
|
||||
|
||||
private int foo;
|
||||
|
||||
private String bar;
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public int getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(int foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class TestConfigurationWithValidationAndInterface {
|
||||
|
||||
@Bean
|
||||
public ValidatedPropertiesImpl testProperties() {
|
||||
return new ValidatedPropertiesImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ValidatedProperties {
|
||||
|
||||
String getFoo();
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
@Validated
|
||||
public static class ValidatedPropertiesImpl implements ValidatedProperties {
|
||||
|
||||
@NotNull
|
||||
private String foo;
|
||||
|
||||
@Override
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
@Validated
|
||||
public static class PropertyWithValue {
|
||||
|
||||
@Value("${default.value}")
|
||||
private String value;
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer configurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class PrototypePropertiesConfig {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConfigurationProperties("example")
|
||||
public PrototypeBean prototypeBean() {
|
||||
return new PrototypeBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class PropertySourcesPlaceholderConfigurerConfiguration {
|
||||
|
||||
@Bean
|
||||
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PrototypeBean {
|
||||
|
||||
private String one;
|
||||
|
||||
private String two;
|
||||
|
||||
public String getOne() {
|
||||
return this.one;
|
||||
}
|
||||
|
||||
public void setOne(String one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public String getTwo() {
|
||||
return this.two;
|
||||
}
|
||||
|
||||
public void setTwo(String two) {
|
||||
this.two = two;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class ConfigurationPropertiesWithFactoryBean {
|
||||
|
||||
public static boolean factoryBeanInit;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
// Must be a raw type
|
||||
static class FactoryBeanTester implements FactoryBean, InitializingBean {
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
ConfigurationPropertiesWithFactoryBean.factoryBeanInit = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(PropertyWithoutConfigurationPropertiesAnnotation.class)
|
||||
public static class ConfigurationPropertiesWithoutAnnotation {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class MultiplePropertySourcesPlaceholderConfigurer {
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer configurer1() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public static PropertySourcesPlaceholderConfigurer configurer2() {
|
||||
return new PropertySourcesPlaceholderConfigurer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PropertyWithoutConfigurationPropertiesAnnotation {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
public Converter<String, Person> personConverter() {
|
||||
return new PersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class NonQualifiedConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
public Converter<String, Person> personConverter() {
|
||||
return new PersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PersonConverter implements Converter<String, Person> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Person convert(String source) {
|
||||
String[] content = StringUtils.split(source, " ");
|
||||
return new Person(content[0], content[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class GenericConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationPropertiesBinding
|
||||
public GenericConverter genericPersonConverter() {
|
||||
return new GenericPersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class NonQualifiedGenericConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
public GenericConverter genericPersonConverter() {
|
||||
return new GenericPersonConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class GenericPersonConverter implements GenericConverter {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return Collections.singleton(new ConvertiblePair(String.class, Person.class));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object convert(@Nullable Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
String[] content = StringUtils.split((String) source, " ");
|
||||
return new Person(content[0], content[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
public static class PersonProperty {
|
||||
|
||||
private Person person;
|
||||
|
||||
public Person getPerson() {
|
||||
return this.person;
|
||||
}
|
||||
|
||||
public void setPerson(Person person) {
|
||||
this.person = person;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
private final String firstName;
|
||||
|
||||
private final String lastName;
|
||||
|
||||
Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties(prefix = "sample")
|
||||
static class NumericKeyConfiguration {
|
||||
|
||||
private Map<String, Foo> foos = new LinkedHashMap<>();
|
||||
|
||||
public Map<String, Foo> getFoos() {
|
||||
return this.foos;
|
||||
}
|
||||
|
||||
static class Foo {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
static class GenericConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties("foo")
|
||||
public AGenericClass<String> aBeanToBind() {
|
||||
return new AGenericClass<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class AGenericClass<T> {
|
||||
|
||||
private T bar;
|
||||
|
||||
public T getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(T bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1572
File diff suppressed because it is too large
Load Diff
-679
@@ -1,679 +0,0 @@
|
||||
/*
|
||||
* 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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.bind.BindException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.support.TestPropertySourceUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link EnableConfigurationProperties}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class EnableConfigurationPropertiesTests {
|
||||
|
||||
private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@After
|
||||
public void close() {
|
||||
System.clearProperty("name");
|
||||
System.clearProperty("nested.name");
|
||||
System.clearProperty("nested_name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicPropertiesBinding() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1);
|
||||
assertThat(this.context.containsBean(TestProperties.class.getName())).isTrue();
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemPropertiesBinding() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
System.setProperty("name", "foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedSystemPropertiesBinding() {
|
||||
this.context.register(NestedConfiguration.class);
|
||||
System.setProperty("name", "foo");
|
||||
System.setProperty("nested.name", "bar");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(NestedProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(NestedProperties.class).name).isEqualTo("foo");
|
||||
assertThat(this.context.getBean(NestedProperties.class).nested.name)
|
||||
.isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrictPropertiesBinding() {
|
||||
removeSystemProperties();
|
||||
this.context.register(StrictTestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(StrictTestProperties.class))
|
||||
.hasSize(1);
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesEmbeddedBinding() {
|
||||
this.context.register(EmbeddedTestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"spring.foo.name=foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(EmbeddedTestProperties.class))
|
||||
.hasSize(1);
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionOnValidation() {
|
||||
this.context.register(ExceptionIfInvalidTestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name:foo");
|
||||
this.thrown.expectCause(Matchers.instanceOf(BindException.class));
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExceptionOnValidationWithoutValidated() {
|
||||
this.context.register(IgnoredIfInvalidButNotValidatedTestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name:foo");
|
||||
this.context.refresh();
|
||||
IgnoredIfInvalidButNotValidatedTestProperties bean = this.context
|
||||
.getBean(IgnoredIfInvalidButNotValidatedTestProperties.class);
|
||||
assertThat(bean.getDescription()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedPropertiesBinding() {
|
||||
this.context.register(NestedConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo", "nested.name=bar");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(NestedProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(NestedProperties.class).name).isEqualTo("foo");
|
||||
assertThat(this.context.getBean(NestedProperties.class).nested.name)
|
||||
.isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicPropertiesBindingWithAnnotationOnBaseClass() {
|
||||
this.context.register(DerivedConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(DerivedProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(BaseProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayPropertiesBinding() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo", "array=1,2,3");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(TestProperties.class).getArray()).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionPropertiesBindingFromYamlArray() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo", "list[0]=1", "list[1]=2");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(TestProperties.class).getList()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollectionPropertiesBindingWithOver256Elements() {
|
||||
this.context.register(TestConfiguration.class);
|
||||
List<String> pairs = new ArrayList<>();
|
||||
pairs.add("name:foo");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
pairs.add("list[" + i + "]:" + i);
|
||||
}
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
pairs.toArray(new String[] {}));
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(TestProperties.class).getList()).hasSize(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesBindingWithoutAnnotation() {
|
||||
this.context.register(InvalidConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name:foo");
|
||||
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("No ConfigurationProperties annotation found");
|
||||
this.context.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesBindingWithoutAnnotationValue() {
|
||||
this.context.register(MoreConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=foo");
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(MoreProperties.class)).hasSize(1);
|
||||
assertThat(this.context.getBean(MoreProperties.class).name).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesBindingWithDefaultsInXml() {
|
||||
this.context.register(TestConfiguration.class, DefaultXmlConfiguration.class);
|
||||
this.context.refresh();
|
||||
String[] beanNames = this.context.getBeanNamesForType(TestProperties.class);
|
||||
assertThat(beanNames).as("Wrong beans").containsExactly(beanNames);
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertiesBindingWithDefaultsInBeanMethod() {
|
||||
this.context.register(DefaultConfiguration.class);
|
||||
this.context.refresh();
|
||||
String[] beanNames = this.context.getBeanNamesForType(TestProperties.class);
|
||||
assertThat(beanNames).as("Wrong beans").containsExactly(beanNames);
|
||||
assertThat(this.context.getBean(TestProperties.class).name).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingWithTwoBeans() {
|
||||
this.context.register(MoreConfiguration.class, TestConfiguration.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class).length)
|
||||
.isEqualTo(1);
|
||||
assertThat(this.context.getBeanNamesForType(MoreProperties.class).length)
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingWithParentContext() {
|
||||
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
|
||||
parent.register(TestConfiguration.class);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(parent, "name=parent");
|
||||
parent.refresh();
|
||||
this.context.setParent(parent);
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"name=child");
|
||||
this.context.register(TestConfiguration.class, TestConsumer.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class).length)
|
||||
.isEqualTo(0);
|
||||
assertThat(parent.getBeanNamesForType(TestProperties.class).length).isEqualTo(1);
|
||||
assertThat(this.context.getBean(TestConsumer.class).getName())
|
||||
.isEqualTo("parent");
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingOnlyParentContext() {
|
||||
AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(parent, "name=foo");
|
||||
parent.register(TestConfiguration.class);
|
||||
parent.refresh();
|
||||
this.context.setParent(parent);
|
||||
this.context.register(TestConsumer.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBeanNamesForType(TestProperties.class).length)
|
||||
.isEqualTo(0);
|
||||
assertThat(parent.getBeanNamesForType(TestProperties.class).length).isEqualTo(1);
|
||||
assertThat(this.context.getBean(TestConsumer.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleAutoConfig() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"external.name=foo");
|
||||
this.context.register(ExampleConfig.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitType() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"external.name=foo");
|
||||
this.context.register(AnotherExampleConfig.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.containsBean("external-" + External.class.getName()))
|
||||
.isTrue();
|
||||
assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleExplicitTypes() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"external.name=foo", "another.name=bar");
|
||||
this.context.register(FurtherExampleConfig.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo");
|
||||
assertThat(this.context.getBean(Another.class).getName()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindingWithMapKeyWithPeriod() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"mymap.key1.key2:value12", "mymap.key3:value3");
|
||||
this.context.register(ResourceBindingPropertiesWithMap.class);
|
||||
this.context.refresh();
|
||||
ResourceBindingPropertiesWithMap bean = this.context
|
||||
.getBean(ResourceBindingPropertiesWithMap.class);
|
||||
assertThat(bean.mymap.get("key3")).isEqualTo("value3");
|
||||
assertThat(bean.mymap.get("key1.key2")).isEqualTo("value12");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAnnotatedBean() {
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context,
|
||||
"external.name=bar", "spam.name=foo");
|
||||
this.context.register(TestConfigurationWithAnnotatedBean.class);
|
||||
this.context.refresh();
|
||||
assertThat(this.context.getBean(External.class).getName()).isEqualTo("foo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict tests need a known set of properties so we remove system items which may be
|
||||
* environment specific.
|
||||
*/
|
||||
private void removeSystemProperties() {
|
||||
MutablePropertySources sources = this.context.getEnvironment()
|
||||
.getPropertySources();
|
||||
sources.remove("systemProperties");
|
||||
sources.remove("systemEnvironment");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties
|
||||
public static class TestConfigurationWithAnnotatedBean {
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spam")
|
||||
public External testProperties() {
|
||||
return new External();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(TestProperties.class)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(StrictTestProperties.class)
|
||||
protected static class StrictTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(EmbeddedTestProperties.class)
|
||||
protected static class EmbeddedTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ExceptionIfInvalidTestProperties.class)
|
||||
protected static class ExceptionIfInvalidTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(IgnoredIfInvalidButNotValidatedTestProperties.class)
|
||||
protected static class IgnoredIfInvalidButNotValidatedTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DerivedProperties.class)
|
||||
protected static class DerivedConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(NestedProperties.class)
|
||||
protected static class NestedConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class DefaultConfiguration {
|
||||
|
||||
@Bean
|
||||
public TestProperties testProperties() {
|
||||
TestProperties test = new TestProperties();
|
||||
test.setName("bar");
|
||||
return test;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ImportResource("org/springframework/boot/context/properties/testProperties.xml")
|
||||
protected static class DefaultXmlConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties
|
||||
@Configuration
|
||||
public static class ExampleConfig {
|
||||
|
||||
@Bean
|
||||
public External external() {
|
||||
return new External();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(External.class)
|
||||
@Configuration
|
||||
public static class AnotherExampleConfig {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties({ External.class, Another.class })
|
||||
@Configuration
|
||||
public static class FurtherExampleConfig {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties({ SystemEnvVar.class })
|
||||
@Configuration
|
||||
public static class SystemExampleConfig {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "external")
|
||||
public static class External {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "another")
|
||||
public static class Another {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "spring_test_external")
|
||||
public static class SystemEnvVar {
|
||||
|
||||
private String val;
|
||||
|
||||
public String getVal() {
|
||||
return this.val;
|
||||
}
|
||||
|
||||
public void setVal(String val) {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
protected static class TestConsumer {
|
||||
|
||||
@Autowired
|
||||
private TestProperties properties;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
assertThat(this.properties).isNotNull();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.properties.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(MoreProperties.class)
|
||||
protected static class MoreConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(InvalidConfiguration.class)
|
||||
protected static class InvalidConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
protected static class NestedProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
private final Nested nested = new Nested();
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
protected static class Nested {
|
||||
|
||||
private String name;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
protected static class BaseProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class DerivedProperties extends BaseProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
protected static class TestProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
private int[] array;
|
||||
|
||||
private List<Integer> list = new ArrayList<>();
|
||||
|
||||
// No getter - you should be able to bind to a write-only bean
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setArray(int... values) {
|
||||
this.array = values;
|
||||
}
|
||||
|
||||
public int[] getArray() {
|
||||
return this.array;
|
||||
}
|
||||
|
||||
public List<Integer> getList() {
|
||||
return this.list;
|
||||
}
|
||||
|
||||
public void setList(List<Integer> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(ignoreUnknownFields = false)
|
||||
protected static class StrictTestProperties extends TestProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "spring.foo")
|
||||
protected static class EmbeddedTestProperties extends TestProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
@Validated
|
||||
protected static class ExceptionIfInvalidTestProperties extends TestProperties {
|
||||
|
||||
@NotNull
|
||||
private String description;
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
protected static class IgnoredIfInvalidButNotValidatedTestProperties
|
||||
extends TestProperties {
|
||||
|
||||
@NotNull
|
||||
private String description;
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
protected static class MoreProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// No getter - you should be able to bind to a write-only bean
|
||||
|
||||
}
|
||||
|
||||
// No annotation
|
||||
protected static class InvalidProperties {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties
|
||||
@ConfigurationProperties
|
||||
protected static class ResourceBindingPropertiesWithMap {
|
||||
|
||||
private Map<String, String> mymap;
|
||||
|
||||
public void setMymap(Map<String, String> mymap) {
|
||||
this.mymap = mymap;
|
||||
}
|
||||
|
||||
public Map<String, String> getMymap() {
|
||||
return this.mymap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 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.
|
||||
@@ -24,6 +24,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
|
||||
@@ -140,6 +141,20 @@ public class BindableTests {
|
||||
.containsExactly(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationWhenMatchShouldReuturnAnnotation() {
|
||||
Test annotation = AnnotationUtils.synthesizeAnnotation(Test.class);
|
||||
assertThat(Bindable.of(String.class).withAnnotations(annotation)
|
||||
.getAnnotation(Test.class)).isSameAs(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationWhenNoMatchShouldReturnNull() {
|
||||
Test annotation = AnnotationUtils.synthesizeAnnotation(Test.class);
|
||||
assertThat(Bindable.of(String.class).withAnnotations(annotation)
|
||||
.getAnnotation(Bean.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toStringShouldShowDetails() {
|
||||
Annotation annotation = AnnotationUtils
|
||||
|
||||
+9
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
* Copyright 2012-2018 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.
|
||||
@@ -127,8 +127,8 @@ public class ValidationBindHandlerTests {
|
||||
BindValidationException cause = bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"),
|
||||
Bindable.of(ExampleValidatedWithNestedBean.class), this.handler));
|
||||
assertThat(cause.getValidationErrors().getName().toString())
|
||||
.isEqualTo("foo.nested");
|
||||
assertThat(cause.getValidationErrors().getName().toString()).isEqualTo("foo");
|
||||
assertThat(cause.getMessage()).contains("nested.age");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -144,11 +144,13 @@ public class ValidationBindHandlerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindShouldNotValidateWithoutAnnotation() {
|
||||
public void bindShouldValidateWithoutAnnotation() {
|
||||
ExampleNonValidatedBean existingValue = new ExampleNonValidatedBean();
|
||||
this.binder.bind(ConfigurationPropertyName.of("foo"), Bindable
|
||||
.of(ExampleNonValidatedBean.class).withExistingValue(existingValue),
|
||||
this.handler);
|
||||
bindAndExpectValidationError(
|
||||
() -> this.binder.bind(ConfigurationPropertyName.of("foo"),
|
||||
Bindable.of(ExampleNonValidatedBean.class)
|
||||
.withExistingValue(existingValue),
|
||||
this.handler));
|
||||
}
|
||||
|
||||
private BindValidationException bindAndExpectValidationError(Runnable action) {
|
||||
@@ -156,8 +158,6 @@ public class ValidationBindHandlerTests {
|
||||
action.run();
|
||||
}
|
||||
catch (BindException ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
BindValidationException cause = (BindValidationException) ex.getCause();
|
||||
return cause;
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean
|
||||
id="org.springframework.boot.context.properties.EnableConfigurationPropertiesTests$TestProperties"
|
||||
class="org.springframework.boot.context.properties.EnableConfigurationPropertiesTests$TestProperties">
|
||||
id="org.springframework.boot.context.properties.ConfigurationPropertiesTests$BasicProperties"
|
||||
class="org.springframework.boot.context.properties.ConfigurationPropertiesTests$BasicProperties">
|
||||
<property name="name" value="bar"/>
|
||||
</bean>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user