Commit 221ff0ff authored by Phillip Webb's avatar Phillip Webb

Polish configuration property bean classes

Refactor the `org.springframework.boot.actuate.context` package
with the following changes:

- Deprecate several classes which would ideally be internal

- Replace `ConfigurationBeanFactoryMetadata` with a new
  `ConfigurationPropertiesBean` class to better reflect that we no
  longer maintain meta-data directly.

- Use constructor injection and final fields whenever possible

- Rename `ConfiguraionPropertiesBeanDefinition` to
  `ConfigurationPropertiesValueObjectBeanDefinition` to align
  with the binder changes made in commit 0b3015e4

- Add additional tests

Closes gh-16903
parent 3b0c4b18
...@@ -49,8 +49,8 @@ import org.springframework.beans.BeansException; ...@@ -49,8 +49,8 @@ import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.Sanitizer; import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
...@@ -108,36 +108,17 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext ...@@ -108,36 +108,17 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
private ContextConfigurationProperties describeConfigurationProperties(ApplicationContext context, private ContextConfigurationProperties describeConfigurationProperties(ApplicationContext context,
ObjectMapper mapper) { ObjectMapper mapper) {
ConfigurationBeanFactoryMetadata beanFactoryMetadata = getBeanFactoryMetadata(context); Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
Map<String, Object> beans = getConfigurationPropertiesBeans(context, beanFactoryMetadata); Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = new HashMap<>();
Map<String, ConfigurationPropertiesBeanDescriptor> beanDescriptors = new HashMap<>();
beans.forEach((beanName, bean) -> { beans.forEach((beanName, bean) -> {
String prefix = extractPrefix(context, beanFactoryMetadata, beanName); String prefix = bean.getAnnotation().prefix();
beanDescriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix, descriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix,
sanitize(prefix, safeSerialize(mapper, bean, prefix)))); sanitize(prefix, safeSerialize(mapper, bean.getInstance(), prefix))));
}); });
return new ContextConfigurationProperties(beanDescriptors, return new ContextConfigurationProperties(descriptors,
(context.getParent() != null) ? context.getParent().getId() : null); (context.getParent() != null) ? context.getParent().getId() : null);
} }
private ConfigurationBeanFactoryMetadata getBeanFactoryMetadata(ApplicationContext context) {
Map<String, ConfigurationBeanFactoryMetadata> beans = context
.getBeansOfType(ConfigurationBeanFactoryMetadata.class);
if (beans.size() == 1) {
return beans.values().iterator().next();
}
return null;
}
private Map<String, Object> getConfigurationPropertiesBeans(ApplicationContext context,
ConfigurationBeanFactoryMetadata beanFactoryMetadata) {
Map<String, Object> beans = new HashMap<>(context.getBeansWithAnnotation(ConfigurationProperties.class));
if (beanFactoryMetadata != null) {
beans.putAll(beanFactoryMetadata.getBeansWithFactoryAnnotation(ConfigurationProperties.class));
}
return beans;
}
/** /**
* Cautiously serialize the bean to a map (returning a map with an error message * Cautiously serialize the bean to a map (returning a map with an error message
* instead of throwing an exception if there is a problem). * instead of throwing an exception if there is a problem).
...@@ -197,30 +178,6 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext ...@@ -197,30 +178,6 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter())); new SimpleFilterProvider().setDefaultFilter(new ConfigurationPropertiesPropertyFilter()));
} }
/**
* Extract configuration prefix from
* {@link ConfigurationProperties @ConfigurationProperties} annotation.
* @param context the application context
* @param beanFactoryMetaData the bean factory meta-data
* @param beanName the bean name
* @return the prefix
*/
private String extractPrefix(ApplicationContext context, ConfigurationBeanFactoryMetadata beanFactoryMetaData,
String beanName) {
ConfigurationProperties annotation = context.findAnnotationOnBean(beanName, ConfigurationProperties.class);
if (beanFactoryMetaData != null) {
ConfigurationProperties override = beanFactoryMetaData.findFactoryAnnotation(beanName,
ConfigurationProperties.class);
if (override != null) {
// The @Bean-level @ConfigurationProperties overrides the one at type
// level when binding. Arguably we should render them both, but this one
// might be the most relevant for a starting point.
annotation = override;
}
}
return annotation.prefix();
}
/** /**
* Sanitize all unwanted configuration properties to avoid leaking of sensitive * Sanitize all unwanted configuration properties to avoid leaking of sensitive
* information. * information.
......
...@@ -24,6 +24,8 @@ import java.util.Map; ...@@ -24,6 +24,8 @@ import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
...@@ -31,12 +33,14 @@ import org.springframework.context.ConfigurableApplicationContext; ...@@ -31,12 +33,14 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
/** /**
* Utility class to memorize {@code @Bean} definition meta data during initialization of * Utility class to memorize {@code @Bean} definition metadata during initialization of
* the bean factory. * the bean factory.
* *
* @author Dave Syer * @author Dave Syer
* @since 1.1.0 * @since 1.1.0
* @deprecated since 2.2.0 in favor of {@link ConfigurationPropertiesBean}
*/ */
@Deprecated
public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware { public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware {
/** /**
...@@ -77,4 +81,13 @@ public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware ...@@ -77,4 +81,13 @@ public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware
this.applicationContext = (ConfigurableApplicationContext) applicationContext; this.applicationContext = (ConfigurableApplicationContext) applicationContext;
} }
static void register(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
}
}
} }
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
/**
* Provides access to {@link ConfigurationProperties} beans from an
* {@link ApplicationContext}.
*
* @author Phillip Webb
* @since 2.0.0
* @see #get(ApplicationContext, Object, String)
* @see #getAll(ApplicationContext)
*/
public final class ConfigurationPropertiesBean {
private final String name;
private final Object instance;
private final ConfigurationProperties annotation;
private final Bindable<?> bindTarget;
private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation,
Bindable<?> bindTarget) {
this.name = name;
this.instance = instance;
this.annotation = annotation;
this.bindTarget = bindTarget;
}
/**
* Return the name of the Spring bean.
* @return the bean name
*/
public String getName() {
return this.name;
}
/**
* Return the actual Spring bean instance.
* @return the bean instance
*/
public Object getInstance() {
return this.instance;
}
/**
* Return the {@link ConfigurationProperties} annotation for the bean. The annotation
* may be defined on the bean itself or from the factory method that create the bean
* (usually a {@link Bean @Bean} method).
* @return the configuration properties annotation
*/
public ConfigurationProperties getAnnotation() {
return this.annotation;
}
/**
* Return a {@link Bindable} instance suitable that can be used as a target for the
* {@link Binder}.
* @return a bind target for use with the {@link Binder}
*/
public Bindable<?> asBindTarget() {
return this.bindTarget;
}
/**
* Return all {@link ConfigurationProperties @ConfigurationProperties} beans contained
* in the given application context. Both directly annotated beans, as well as beans
* that have {@link ConfigurationProperties @ConfigurationProperties} annotated
* factory methods are included.
* @param applicationContext the source application context
* @return a map of all configuration properties beans keyed by the bean name
*/
public static Map<String, ConfigurationPropertiesBean> getAll(ApplicationContext applicationContext) {
Assert.notNull(applicationContext, "ApplicationContext must not be null");
if (applicationContext instanceof ConfigurableApplicationContext) {
return getAll((ConfigurableApplicationContext) applicationContext);
}
Map<String, ConfigurationPropertiesBean> propertiesBeans = new LinkedHashMap<>();
applicationContext.getBeansWithAnnotation(ConfigurationProperties.class)
.forEach((beanName, bean) -> propertiesBeans.put(beanName, get(applicationContext, bean, beanName)));
return propertiesBeans;
}
private static Map<String, ConfigurationPropertiesBean> getAll(ConfigurableApplicationContext applicationContext) {
Map<String, ConfigurationPropertiesBean> propertiesBeans = new LinkedHashMap<>();
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
Iterator<String> beanNames = beanFactory.getBeanNamesIterator();
while (beanNames.hasNext()) {
String beanName = beanNames.next();
try {
Object bean = beanFactory.getBean(beanName);
ConfigurationPropertiesBean propertiesBean = get(applicationContext, bean, beanName);
if (propertiesBean != null) {
propertiesBeans.put(beanName, propertiesBean);
}
}
catch (NoSuchBeanDefinitionException ex) {
}
}
return propertiesBeans;
}
/**
* Return a {@link ConfigurationPropertiesBean @ConfigurationPropertiesBean} instance
* for the given bean details or {@code null} if the bean is not a
* {@link ConfigurationProperties @ConfigurationProperties} object. Annotations are
* considered both on the bean itself, as well as any factory method (for example a
* {@link Bean @Bean} method).
* @param applicationContext the source application context
* @param bean the bean to consider
* @param beanName the bean name
* @return a configuration properties bean or {@code null} if the neither the bean or
* factory method are annotated with
* {@link ConfigurationProperties @ConfigurationProperties}
*/
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
ConfigurationProperties annotation = getAnnotation(applicationContext, bean, beanName, factoryMethod,
ConfigurationProperties.class);
if (annotation == null) {
return null;
}
ResolvableType type = (factoryMethod != null) ? ResolvableType.forMethodReturnType(factoryMethod)
: ResolvableType.forClass(bean.getClass());
Validated validated = getAnnotation(applicationContext, bean, beanName, factoryMethod, Validated.class);
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
Bindable<?> bindTarget = Bindable.of(type).withAnnotations(annotations).withExistingValue(bean);
return new ConfigurationPropertiesBean(beanName, bean, annotation, bindTarget);
}
private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
if (applicationContext instanceof ConfigurableApplicationContext) {
return findFactoryMethod((ConfigurableApplicationContext) applicationContext, beanName);
}
return null;
}
private static Method findFactoryMethod(ConfigurableApplicationContext applicationContext, String beanName) {
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
if (beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
if (beanDefinition instanceof RootBeanDefinition) {
return ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod();
}
}
return null;
}
private static <A extends Annotation> A getAnnotation(ApplicationContext applicationContext, Object bean,
String beanName, Method factoryMethod, Class<A> annotationType) {
if (factoryMethod != null) {
A annotation = AnnotationUtils.findAnnotation(factoryMethod, annotationType);
if (annotation != null) {
return annotation;
}
}
A annotation = AnnotationUtils.findAnnotation(bean.getClass(), annotationType);
if (annotation != null) {
return annotation;
}
if (AopUtils.isAopProxy(bean)) {
return AnnotationUtils.findAnnotation(AopUtils.getTargetClass(bean), annotationType);
}
return null;
}
}
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Registers a bean definition for a type annotated with
* {@link ConfigurationProperties @ConfigurationProperties} using the prefix of the
* annotation in the bean name.
*
* @author Madhura Bhave
*/
final class ConfigurationPropertiesBeanDefinitionRegistrar {
private static final boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
private ConfigurationPropertiesBeanDefinitionRegistrar() {
}
static void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, Class<?> type) {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
String name = getName(type, annotation);
if (!containsBeanDefinition(beanFactory, name)) {
registerBeanDefinition(registry, beanFactory, name, type, annotation);
}
}
private static String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
private static boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {
if (beanFactory.containsBeanDefinition(name)) {
return true;
}
BeanFactory parent = beanFactory.getParentBeanFactory();
if (parent instanceof ConfigurableListableBeanFactory) {
return containsBeanDefinition((ConfigurableListableBeanFactory) parent, name);
}
return false;
}
private static void registerBeanDefinition(BeanDefinitionRegistry registry,
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
Assert.isTrue(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
registry.registerBeanDefinition(name, createBeanDefinition(beanFactory, name, type));
}
private static BeanDefinition createBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name,
Class<?> type) {
if (canBindAtCreationTime(type)) {
return ConfigurationPropertiesBeanDefinition.from(beanFactory, name, type);
}
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
}
private static boolean canBindAtCreationTime(Class<?> type) {
List<Constructor<?>> constructors = determineConstructors(type);
return (constructors.size() == 1 && constructors.get(0).getParameterCount() > 0);
}
private static List<Constructor<?>> determineConstructors(Class<?> type) {
List<Constructor<?>> constructors = new ArrayList<>();
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
if (primaryConstructor != null) {
constructors.add(primaryConstructor);
}
}
else {
constructors.addAll(Arrays.asList(type.getDeclaredConstructors()));
}
return constructors;
}
}
...@@ -13,45 +13,101 @@ ...@@ -13,45 +13,101 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.util.Arrays; import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.KotlinDetector;
import org.springframework.util.MultiValueMap; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* {@link ImportBeanDefinitionRegistrar} for configuration properties support. * Delegate used by {@link EnableConfigurationPropertiesRegistrar} and
* {@link ConfigurationPropertiesScanRegistrar} to register a bean definition for a
* {@link ConfigurationProperties @ConfigurationProperties} class.
* *
* @author Dave Syer * @author Madhura Bhave
* @author Christian Dupuis * @author Phillip Webb
* @author Stephane Nicoll
*/ */
class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar { final class ConfigurationPropertiesBeanRegistrar {
private final BeanDefinitionRegistry registry;
private final BeanFactory beanFactory;
ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
this.beanFactory = (BeanFactory) this.registry;
}
void register(Class<?> type) {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
register(type, annotation);
}
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String name = getName(type, annotation);
if (!containsBeanDefinition(name)) {
registerBeanDefinition(name, type, annotation);
}
}
private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
private boolean containsBeanDefinition(String name) {
return containsBeanDefinition(this.beanFactory, name);
}
private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
if (beanFactory instanceof ListableBeanFactory
&& ((ListableBeanFactory) beanFactory).containsBeanDefinition(name)) {
return true;
}
if (beanFactory instanceof HierarchicalBeanFactory) {
return containsBeanDefinition(((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(), name);
}
return false;
}
@Override private void registerBeanDefinition(String beanName, Class<?> type,
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { MergedAnnotation<ConfigurationProperties> annotation) {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry; Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
getTypes(metadata).forEach( + " annotation found on '" + type.getName() + "'.");
(type) -> ConfigurationPropertiesBeanDefinitionRegistrar.register(registry, beanFactory, type)); this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
} }
private List<Class<?>> getTypes(AnnotationMetadata metadata) { private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
MultiValueMap<String, Object> attributes = metadata if (isValueObject(type)) {
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false); return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList()); }
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
} }
private List<Class<?>> collectClasses(List<?> values) { private boolean isValueObject(Class<?> type) {
return values.stream().flatMap((value) -> Arrays.stream((Class<?>[]) value)) if (KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type)) {
.filter((type) -> void.class != type).collect(Collectors.toList()); Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
if (primaryConstructor != null) {
return primaryConstructor.getParameterCount() > 0;
}
}
Constructor<?>[] constructors = type.getDeclaredConstructors();
return constructors.length == 1 && constructors[0].getParameterCount() > 0;
} }
} }
...@@ -33,6 +33,10 @@ public class ConfigurationPropertiesBindException extends BeanCreationException ...@@ -33,6 +33,10 @@ public class ConfigurationPropertiesBindException extends BeanCreationException
private final ConfigurationProperties annotation; private final ConfigurationProperties annotation;
ConfigurationPropertiesBindException(ConfigurationPropertiesBean bean, Exception cause) {
this(bean.getName(), bean.getInstance().getClass(), bean.getAnnotation(), cause);
}
ConfigurationPropertiesBindException(String beanName, Class<?> beanType, ConfigurationProperties annotation, ConfigurationPropertiesBindException(String beanName, Class<?> beanType, ConfigurationProperties annotation,
Exception cause) { Exception cause) {
super(beanName, getMessage(beanType, annotation), cause); super(beanName, getMessage(beanType, annotation), cause);
......
...@@ -23,6 +23,11 @@ import java.util.stream.Collectors; ...@@ -23,6 +23,11 @@ import java.util.stream.Collectors;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
...@@ -45,43 +50,36 @@ import org.springframework.validation.Validator; ...@@ -45,43 +50,36 @@ import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
/** /**
* Internal class by the {@link ConfigurationPropertiesBindingPostProcessor} to handle the * Internal class used by the {@link ConfigurationPropertiesBindingPostProcessor} to
* actual {@link ConfigurationProperties @ConfigurationProperties} binding. * handle the actual {@link ConfigurationProperties @ConfigurationProperties} binding.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
*/ */
class ConfigurationPropertiesBinder implements ApplicationContextAware { class ConfigurationPropertiesBinder {
/** private static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";
* The bean name that this binder is registered with.
*/ private static final String FACTORY_BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory";
static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";
private final String validatorBeanName; private static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
private ApplicationContext applicationContext; private final ApplicationContext applicationContext;
private PropertySources propertySources; private final PropertySources propertySources;
private Validator configurationPropertiesValidator; private final Validator configurationPropertiesValidator;
private boolean jsr303Present; private final boolean jsr303Present;
private volatile Validator jsr303Validator; private volatile Validator jsr303Validator;
private volatile Binder binder; private volatile Binder binder;
ConfigurationPropertiesBinder(String validatorBeanName) { ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
this.validatorBeanName = validatorBeanName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources(); this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext, this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext);
this.validatorBeanName);
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext); this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
} }
...@@ -103,10 +101,9 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware { ...@@ -103,10 +101,9 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
return annotation; return annotation;
} }
private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext, private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) {
String validatorBeanName) { if (applicationContext.containsBean(VALIDATOR_BEAN_NAME)) {
if (applicationContext.containsBean(validatorBeanName)) { return applicationContext.getBean(VALIDATOR_BEAN_NAME, Validator.class);
return applicationContext.getBean(validatorBeanName, Validator.class);
} }
return null; return null;
} }
...@@ -183,4 +180,46 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware { ...@@ -183,4 +180,46 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
return null; return null;
} }
static void register(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(FACTORY_BEAN_NAME)) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBinder.Factory.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(ConfigurationPropertiesBinder.FACTORY_BEAN_NAME, definition);
}
if (!registry.containsBeanDefinition(BEAN_NAME)) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBinder.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
definition.setFactoryBeanName(FACTORY_BEAN_NAME);
definition.setFactoryMethodName("create");
registry.registerBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME, definition);
}
}
static ConfigurationPropertiesBinder get(BeanFactory beanFactory) {
return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
}
/**
* Factory bean used to create the {@link ConfigurationPropertiesBinder}. The bean
* needs to be {@link ApplicationContextAware} since we can't directly inject an
* {@link ApplicationContext} into the constructor without causing eager
* {@link FactoryBean} initialization.
*/
static class Factory implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
ConfigurationPropertiesBinder create() {
return new ConfigurationPropertiesBinder(this.applicationContext);
}
}
} }
...@@ -16,23 +16,18 @@ ...@@ -16,23 +16,18 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered; import org.springframework.core.PriorityOrdered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.validation.annotation.Validated; import org.springframework.util.Assert;
/** /**
* {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
...@@ -56,16 +51,26 @@ public class ConfigurationPropertiesBindingPostProcessor ...@@ -56,16 +51,26 @@ public class ConfigurationPropertiesBindingPostProcessor
/** /**
* The bean name of the configuration properties validator. * The bean name of the configuration properties validator.
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link ConfigurationPropertiesBindingPostProcessorRegistrar#VALIDATOR_BEAN_NAME} * {@link EnableConfigurationProperties#VALIDATOR_BEAN_NAME}
*/ */
@Deprecated @Deprecated
public static final String VALIDATOR_BEAN_NAME = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME; public static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
private ApplicationContext applicationContext; private ApplicationContext applicationContext;
private ConfigurationPropertiesBinder configurationPropertiesBinder; private BeanDefinitionRegistry registry;
private ConfigurationPropertiesBinder binder;
/**
* Create a new {@link ConfigurationPropertiesBindingPostProcessor} instance.
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties @EnableConfigurationProperties} or
* {@link ConfigurationPropertiesBindingPostProcessor#register(BeanDefinitionRegistry)}
*/
@Deprecated
public ConfigurationPropertiesBindingPostProcessor() {
}
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
...@@ -76,10 +81,8 @@ public class ConfigurationPropertiesBindingPostProcessor ...@@ -76,10 +81,8 @@ public class ConfigurationPropertiesBindingPostProcessor
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
// We can't use constructor injection of the application context because // We can't use constructor injection of the application context because
// it causes eager factory bean initialization // it causes eager factory bean initialization
this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME, this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
ConfigurationBeanFactoryMetadata.class); this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
this.configurationPropertiesBinder = this.applicationContext.getBean(ConfigurationPropertiesBinder.BEAN_NAME,
ConfigurationPropertiesBinder.class);
} }
@Override @Override
...@@ -89,51 +92,39 @@ public class ConfigurationPropertiesBindingPostProcessor ...@@ -89,51 +92,39 @@ public class ConfigurationPropertiesBindingPostProcessor
@Override @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class); ConfigurationPropertiesBean configurationPropertiesBean = ConfigurationPropertiesBean
if (annotation != null && !hasBeenBound(beanName)) { .get(this.applicationContext, bean, beanName);
bind(bean, beanName, annotation); if (configurationPropertiesBean != null && !hasBoundValueObject(beanName)) {
try {
this.binder.bind(configurationPropertiesBean.asBindTarget());
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(configurationPropertiesBean, ex);
}
} }
return bean; return bean;
} }
private boolean hasBeenBound(String beanName) { private boolean hasBoundValueObject(String beanName) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) this.applicationContext return this.registry.containsBeanDefinition(beanName) && this.registry
.getAutowireCapableBeanFactory(); .getBeanDefinition(beanName) instanceof ConfigurationPropertiesValueObjectBeanDefinition;
if (registry.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
return beanDefinition instanceof ConfigurationPropertiesBeanDefinition;
}
return false;
} }
private void bind(Object bean, String beanName, ConfigurationProperties annotation) { /**
ResolvableType type = getBeanType(bean, beanName); * Register a {@link ConfigurationPropertiesBindingPostProcessor} bean if one is not
Validated validated = getAnnotation(bean, beanName, Validated.class); * already registered.
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated } * @param registry the bean definition registry
: new Annotation[] { annotation }; * @since 2.2.0
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations); */
try { public static void register(BeanDefinitionRegistry registry) {
this.configurationPropertiesBinder.bind(target); Assert.notNull(registry, "Registry must not be null");
} if (!registry.containsBeanDefinition(BEAN_NAME)) {
catch (Exception ex) { GenericBeanDefinition definition = new GenericBeanDefinition();
throw new ConfigurationPropertiesBindException(beanName, bean.getClass(), annotation, ex); definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
} definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
} registry.registerBeanDefinition(BEAN_NAME, definition);
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(), type);
} }
return annotation; ConfigurationPropertiesBinder.register(registry);
} }
} }
...@@ -16,9 +16,7 @@ ...@@ -16,9 +16,7 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; 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.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
...@@ -29,45 +27,20 @@ import org.springframework.core.type.AnnotationMetadata; ...@@ -29,45 +27,20 @@ import org.springframework.core.type.AnnotationMetadata;
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb * @author Phillip Webb
* @since 1.0.0 * @since 1.0.0
* @deprecated since 2.2.0 in favor of
* {@link EnableConfigurationProperties @EnableConfigurationProperties}
*/ */
@Deprecated
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar { public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {
/**
* The bean name of the configuration properties validator.
*/
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
@Override @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME)) { // Spring Cloud Function may call this with a null importingClassMetadata
registerConfigurationPropertiesBinder(registry); if (importingClassMetadata == null) {
} EnableConfigurationPropertiesRegistrar.registerInfrastructureBeans(registry);
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) { return;
registerConfigurationPropertiesBindingPostProcessor(registry);
registerConfigurationBeanFactoryMetadata(registry);
} }
} new EnableConfigurationPropertiesRegistrar().registerBeanDefinitions(importingClassMetadata, registry);
private void registerConfigurationPropertiesBinder(BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(ConfigurationPropertiesBinder.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
definition.getConstructorArgumentValues().addIndexedArgumentValue(0, VALIDATOR_BEAN_NAME);
registry.registerBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME, definition);
}
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);
} }
} }
...@@ -23,6 +23,7 @@ import java.lang.annotation.Target; ...@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
/** /**
* Configures the base packages used when scanning for * Configures the base packages used when scanning for
...@@ -30,6 +31,9 @@ import org.springframework.core.annotation.AliasFor; ...@@ -30,6 +31,9 @@ import org.springframework.core.annotation.AliasFor;
* {@link #basePackageClasses()}, {@link #basePackages()} or its alias {@link #value()} * {@link #basePackageClasses()}, {@link #basePackages()} or its alias {@link #value()}
* may be specified to define specific packages to scan. If specific packages are not * may be specified to define specific packages to scan. If specific packages are not
* defined scanning will occur from the package of the class with this annotation. * defined scanning will occur from the package of the class with this annotation.
* <p>
* Note: Classes annotated or meta-annotated with {@link Component @Component} will not be
* picked up by this annotation.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.2.0 * @since 2.2.0
...@@ -37,7 +41,8 @@ import org.springframework.core.annotation.AliasFor; ...@@ -37,7 +41,8 @@ import org.springframework.core.annotation.AliasFor;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ ConfigurationPropertiesScanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class }) @Import({ ConfigurationPropertiesScanRegistrar.class })
@EnableConfigurationProperties
public @interface ConfigurationPropertiesScan { public @interface ConfigurationPropertiesScan {
/** /**
......
...@@ -19,12 +19,10 @@ import java.util.Arrays; ...@@ -19,12 +19,10 @@ import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
...@@ -43,18 +41,23 @@ import org.springframework.util.StringUtils; ...@@ -43,18 +41,23 @@ import org.springframework.util.StringUtils;
* {@link ConfigurationProperties @ConfigurationProperties} bean definitions via scanning. * {@link ConfigurationProperties @ConfigurationProperties} bean definitions via scanning.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb
*/ */
class ConfigurationPropertiesScanRegistrar class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar {
implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
private Environment environment; private final Environment environment;
private ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
ConfigurationPropertiesScanRegistrar(Environment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = resourceLoader;
}
@Override @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata); Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
register(registry, (ConfigurableListableBeanFactory) registry, packagesToScan); scan(registry, packagesToScan);
} }
private Set<String> getPackagesToScan(AnnotationMetadata metadata) { private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
...@@ -69,43 +72,43 @@ class ConfigurationPropertiesScanRegistrar ...@@ -69,43 +72,43 @@ class ConfigurationPropertiesScanRegistrar
if (packagesToScan.isEmpty()) { if (packagesToScan.isEmpty()) {
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName())); packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
} }
packagesToScan.removeIf((candidate) -> !StringUtils.hasText(candidate));
return packagesToScan; return packagesToScan;
} }
protected void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory, private void scan(BeanDefinitionRegistry registry, Set<String> packages) {
Set<String> packagesToScan) { ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(registry);
scan(packagesToScan, beanFactory, registry); ClassPathScanningCandidateComponentProvider scanner = getScanner(registry);
for (String basePackage : packages) {
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
register(registrar, candidate.getBeanClassName());
}
}
} }
protected void scan(Set<String> packages, ConfigurableListableBeanFactory beanFactory, private ClassPathScanningCandidateComponentProvider getScanner(BeanDefinitionRegistry registry) {
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.setEnvironment(this.environment); scanner.setEnvironment(this.environment);
scanner.setResourceLoader(this.resourceLoader); scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class)); scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class));
TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter(); TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter();
typeExcludeFilter.setBeanFactory(beanFactory); typeExcludeFilter.setBeanFactory((BeanFactory) registry);
scanner.addExcludeFilter(typeExcludeFilter); scanner.addExcludeFilter(typeExcludeFilter);
for (String basePackage : packages) { return scanner;
if (StringUtils.hasText(basePackage)) { }
scan(beanFactory, registry, scanner, basePackage);
} private void register(ConfigurationPropertiesBeanRegistrar registrar, String className) throws LinkageError {
try {
register(registrar, ClassUtils.forName(className, null));
}
catch (ClassNotFoundException ex) {
// Ignore
} }
} }
private void scan(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry, private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
ClassPathScanningCandidateComponentProvider scanner, String basePackage) throws LinkageError { if (!isComponent(type)) {
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) { registrar.register(type);
String beanClassName = candidate.getBeanClassName();
try {
Class<?> type = ClassUtils.forName(beanClassName, null);
if (!isComponent(type)) {
ConfigurationPropertiesBeanDefinitionRegistrar.register(registry, beanFactory, type);
}
}
catch (ClassNotFoundException ex) {
// Ignore
}
} }
} }
...@@ -113,14 +116,4 @@ class ConfigurationPropertiesScanRegistrar ...@@ -113,14 +116,4 @@ class ConfigurationPropertiesScanRegistrar
return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class); return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class);
} }
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
} }
...@@ -17,54 +17,51 @@ ...@@ -17,54 +17,51 @@
package org.springframework.boot.context.properties; package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
/** /**
* {@link BeanDefinition} that is used for registering * {@link BeanDefinition} that is used for registering
* {@link ConfigurationProperties @ConfigurationProperties} beans that are bound at * {@link ConfigurationProperties @ConfigurationProperties} value object beans that are
* creation time. * bound at creation time.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Madhura Bhave * @author Madhura Bhave
*/ */
final class ConfigurationPropertiesBeanDefinition extends GenericBeanDefinition { final class ConfigurationPropertiesValueObjectBeanDefinition extends GenericBeanDefinition {
static ConfigurationPropertiesBeanDefinition from(ConfigurableListableBeanFactory beanFactory, String beanName, private final BeanFactory beanFactory;
Class<?> type) {
ConfigurationPropertiesBeanDefinition beanDefinition = new ConfigurationPropertiesBeanDefinition(); private final String beanName;
beanDefinition.setBeanClass(type);
beanDefinition.setInstanceSupplier(createBean(beanFactory, beanName, type));
return beanDefinition;
}
private static <T> Supplier<T> createBean(ConfigurableListableBeanFactory beanFactory, String beanName, ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass) {
Class<T> type) { this.beanFactory = beanFactory;
return () -> { this.beanName = beanName;
ConfigurationProperties annotation = getAnnotation(type, ConfigurationProperties.class); setBeanClass(beanClass);
Validated validated = getAnnotation(type, Validated.class); setInstanceSupplier(this::createBean);
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
Bindable<T> bindable = Bindable.of(type).withAnnotations(annotations);
ConfigurationPropertiesBinder binder = beanFactory.getBean(ConfigurationPropertiesBinder.BEAN_NAME,
ConfigurationPropertiesBinder.class);
try {
return binder.bindOrCreate(bindable);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, type, annotation, ex);
}
};
} }
private static <A extends Annotation> A getAnnotation(Class<?> type, Class<A> annotationType) { private Object createBean() {
return AnnotationUtils.findAnnotation(type, annotationType); ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
ResolvableType type = ResolvableType.forClass(getBeanClass());
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(getBeanClass(),
ConfigurationProperties.class);
Validated validated = AnnotationUtils.findAnnotation(getBeanClass(), Validated.class);
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
: new Annotation[] { annotation };
Bindable<Object> bindTarget = Bindable.of(type).withAnnotations(annotations);
try {
return binder.bindOrCreate(bindTarget);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(this.beanName, getBeanClass(), annotation, ex);
}
} }
} }
...@@ -37,9 +37,15 @@ import org.springframework.context.annotation.Import; ...@@ -37,9 +37,15 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Import({ ConfigurationPropertiesBeanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class }) @Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties { public @interface EnableConfigurationProperties {
/**
* The bean name of the configuration properties validator.
* @since 2.2.0
*/
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
/** /**
* Convenient way to quickly register * Convenient way to quickly register
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with * {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
......
/*
* Copyright 2012-2019 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
*
* https://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.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.type.AnnotationMetadata;
/**
* {@link ImportBeanDefinitionRegistrar} for
* {@link EnableConfigurationProperties @EnableConfigurationProperties}.
*
* @author Phillip Webb
*/
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
}
@SuppressWarnings("deprecation")
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
ConfigurationBeanFactoryMetadata.register(registry);
}
}
...@@ -27,6 +27,7 @@ import org.springframework.core.env.ConfigurableEnvironment; ...@@ -27,6 +27,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
/** /**
* Utility to deduce the {@link PropertySources} to use for configuration binding. * Utility to deduce the {@link PropertySources} to use for configuration binding.
...@@ -49,19 +50,9 @@ class PropertySourcesDeducer { ...@@ -49,19 +50,9 @@ class PropertySourcesDeducer {
return configurer.getAppliedPropertySources(); return configurer.getAppliedPropertySources();
} }
MutablePropertySources sources = extractEnvironmentPropertySources(); MutablePropertySources sources = extractEnvironmentPropertySources();
if (sources != null) { Assert.state(sources != null,
return sources;
}
throw new IllegalStateException(
"Unable to obtain PropertySources from PropertySourcesPlaceholderConfigurer or Environment"); "Unable to obtain PropertySources from PropertySourcesPlaceholderConfigurer or Environment");
} return sources;
private MutablePropertySources extractEnvironmentPropertySources() {
Environment environment = this.applicationContext.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
return ((ConfigurableEnvironment) environment).getPropertySources();
}
return null;
} }
private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() { private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
...@@ -78,4 +69,12 @@ class PropertySourcesDeducer { ...@@ -78,4 +69,12 @@ class PropertySourcesDeducer {
return null; return null;
} }
private MutablePropertySources extractEnvironmentPropertySources() {
Environment environment = this.applicationContext.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
return ((ConfigurableEnvironment) environment).getPropertySources();
}
return null;
}
} }
...@@ -13,130 +13,98 @@ ...@@ -13,130 +13,98 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.context.properties;
import java.io.IOException; package org.springframework.boot.context.properties;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/** /**
* Tests for {@link ConfigurationPropertiesBeanRegistrar}. * Tests for {@link ConfigurationPropertiesBeanRegistrar}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
*/ */
class ConfigurationPropertiesBeanRegistrarTests { class ConfigurationPropertiesBeanRegistrarTests {
private final ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(); private BeanDefinitionRegistry registry = new DefaultListableBeanFactory();
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); private ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(this.registry);
@Test
void typeWithDefaultConstructorShouldRegisterGenericBeanDefinition() throws Exception {
this.registrar.registerBeanDefinitions(getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrarTests$FooProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test @Test
void typeWithOneConstructorWithParametersShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception { void registerWhenNotAlreadyRegisteredAddBeanDefinition() {
this.registrar.registerBeanDefinitions(getAnnotationMetadata(TestConfiguration.class), this.beanFactory); String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition( this.registrar.register(BeanConfigurationProperties.class);
"bar-org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrarTests$BarProperties"); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(definition).isNotNull();
assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName());
} }
@Test @Test
void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition() throws Exception { void registerWhenAlreadyContainsNameDoesNotReplace() {
this.registrar.registerBeanDefinitions(getAnnotationMetadata(TestConfiguration.class), this.beanFactory); String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition( this.registry.registerBeanDefinition(beanName, new GenericBeanDefinition());
"bing-org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrarTests$BingProperties"); this.registrar.register(BeanConfigurationProperties.class);
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isNotNull();
assertThat(definition.getBeanClassName()).isNull();
} }
@Test @Test
void typeWithNoAnnotationShouldFail() { void registerWhenNoAnnotationThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalStateException()
.isThrownBy(() -> this.registrar .isThrownBy(() -> this.registrar.register(NoAnnotationConfigurationProperties.class))
.registerBeanDefinitions(getAnnotationMetadata(InvalidConfiguration.class), this.beanFactory)) .withMessageContaining("No ConfigurationProperties annotation found");
.withMessageContaining("No ConfigurationProperties annotation found")
.withMessageContaining(ConfigurationPropertiesBeanRegistrar.class.getName());
} }
@Test @Test
void registrationWithDuplicatedTypeShouldRegisterSingleBeanDefinition() throws IOException { void registerWhenValueObjectRegistersValueObjectBeanDefinition() {
DefaultListableBeanFactory factory = spy(this.beanFactory); String beanName = "valuecp-" + ValueObjectConfigurationProperties.class.getName();
this.registrar.registerBeanDefinitions(getAnnotationMetadata(DuplicateConfiguration.class), factory); this.registrar.register(ValueObjectConfigurationProperties.class);
verify(factory, times(1)).registerBeanDefinition(anyString(), any()); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
} }
@Test @Test
void registrationWithNoTypeShouldNotRegisterAnything() throws IOException { void registerWhenNotValueObjectRegistersGenericBeanDefinition() {
DefaultListableBeanFactory factory = spy(this.beanFactory); String beanName = MultiConstructorBeanConfigurationProperties.class.getName();
this.registrar.registerBeanDefinitions(getAnnotationMetadata(EmptyConfiguration.class), factory); this.registrar.register(MultiConstructorBeanConfigurationProperties.class);
verifyZeroInteractions(factory); BeanDefinition definition = this.registry.getBeanDefinition(beanName);
} assertThat(definition).isInstanceOf(GenericBeanDefinition.class);
private AnnotationMetadata getAnnotationMetadata(Class<?> source) throws IOException {
return new SimpleMetadataReaderFactory().getMetadataReader(source.getName()).getAnnotationMetadata();
}
@EnableConfigurationProperties({ FooProperties.class, BarProperties.class, BingProperties.class })
static class TestConfiguration {
}
@EnableConfigurationProperties(ConfigurationPropertiesBeanRegistrarTests.class)
static class InvalidConfiguration {
}
@EnableConfigurationProperties({ FooProperties.class, FooProperties.class })
static class DuplicateConfiguration {
} }
@EnableConfigurationProperties @ConfigurationProperties(prefix = "beancp")
static class EmptyConfiguration { static class BeanConfigurationProperties {
} }
@ConfigurationProperties(prefix = "foo") static class NoAnnotationConfigurationProperties {
static class FooProperties {
} }
@ConfigurationProperties(prefix = "bar") @ConfigurationProperties(prefix = "valuecp")
static class BarProperties { static class ValueObjectConfigurationProperties {
BarProperties(String foo) { ValueObjectConfigurationProperties(String name) {
} }
} }
@ConfigurationProperties(prefix = "bing") @ConfigurationProperties
static class BingProperties { static class MultiConstructorBeanConfigurationProperties {
BingProperties() { MultiConstructorBeanConfigurationProperties() {
} }
BingProperties(String foo) { MultiConstructorBeanConfigurationProperties(String name) {
} }
} }
......
/*
* Copyright 2012-2019 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
*
* https://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.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.ThrowingConsumer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertiesBean}.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBeanTests {
@Test
void getAllReturnsAll() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NonAnnotatedComponent.class, AnnotatedComponent.class, AnnotatedBeanConfiguration.class)) {
Map<String, ConfigurationPropertiesBean> all = ConfigurationPropertiesBean.getAll(context);
assertThat(all).containsOnlyKeys("annotatedComponent", "annotatedBean");
ConfigurationPropertiesBean component = all.get("annotatedComponent");
assertThat(component.getName()).isEqualTo("annotatedComponent");
assertThat(component.getInstance()).isInstanceOf(AnnotatedComponent.class);
assertThat(component.getAnnotation()).isNotNull();
ConfigurationPropertiesBean bean = all.get("annotatedBean");
assertThat(bean.getName()).isEqualTo("annotatedBean");
assertThat(bean.getInstance()).isInstanceOf(AnnotatedBean.class);
assertThat(bean.getAnnotation()).isNotNull();
}
}
@Test
void getWhenNotAnnotatedReturnsNull() throws Throwable {
get(NonAnnotatedComponent.class, "nonAnnotatedComponent",
(propertiesBean) -> assertThat(propertiesBean).isNull());
}
@Test
void getWhenBeanIsAnnotatedReturnsBean() throws Throwable {
get(AnnotatedComponent.class, "annotatedComponent", (propertiesBean) -> {
assertThat(propertiesBean).isNotNull();
assertThat(propertiesBean.getName()).isEqualTo("annotatedComponent");
assertThat(propertiesBean.getInstance()).isInstanceOf(AnnotatedComponent.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
});
}
@Test
void getWhenFactoryMethodIsAnnotatedReturnsBean() throws Throwable {
get(NonAnnotatedBeanConfiguration.class, "nonAnnotatedBean", (propertiesBean) -> {
assertThat(propertiesBean).isNotNull();
assertThat(propertiesBean.getName()).isEqualTo("nonAnnotatedBean");
assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
});
}
@Test
void getWhenHasFactoryMethodBindsUsingMethodReturnType() throws Throwable {
get(NonAnnotatedGenericBeanConfiguration.class, "nonAnnotatedGenericBean", (propertiesBean) -> {
ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(NonAnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
});
}
@Test
void getWhenHasFactoryMethodWithoutAnnotationBindsUsingMethodType() throws Throwable {
get(AnnotatedGenericBeanConfiguration.class, "annotatedGenericBean", (propertiesBean) -> {
ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
});
}
@Test
void getWhenHasNoFactoryMethodBindsUsingObjectType() throws Throwable {
get(AnnotatedGenericComponent.class, "annotatedGenericComponent", (propertiesBean) -> {
ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericComponent.class);
assertThat(type.getGeneric(0).resolve()).isNull();
});
}
@Test
void getWhenHasFactoryMethodAndBeanAnnotationFavorsFactoryMethod() throws Throwable {
get(AnnotatedBeanConfiguration.class, "annotatedBean",
(propertiesBean) -> assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("factory"));
}
@Test
void getWhenHasValidatedBeanBindsWithBeanAnnotation() throws Throwable {
get(ValidatedBeanConfiguration.class, "validatedBean", (propertiesBean) -> {
Validated validated = propertiesBean.asBindTarget().getAnnotation(Validated.class);
assertThat(validated.value()).containsExactly(BeanGroup.class);
});
}
@Test
void getWhenHasValidatedFactoryMethodBindsWithFactoryMethodAnnotation() throws Throwable {
get(ValidatedMethodConfiguration.class, "annotatedBean", (propertiesBean) -> {
Validated validated = propertiesBean.asBindTarget().getAnnotation(Validated.class);
assertThat(validated.value()).containsExactly(FactoryMethodGroup.class);
});
}
@Test
void getWhenHasValidatedBeanAndFactoryMethodBindsWithFactoryMethodAnnotation() throws Throwable {
get(ValidatedMethodAndBeanConfiguration.class, "validatedBean", (propertiesBean) -> {
Validated validated = propertiesBean.asBindTarget().getAnnotation(Validated.class);
assertThat(validated.value()).containsExactly(FactoryMethodGroup.class);
});
}
private void get(Class<?> configuration, String beanName, ThrowingConsumer<ConfigurationPropertiesBean> consumer)
throws Throwable {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configuration)) {
Object bean = context.getBean(beanName);
consumer.accept(ConfigurationPropertiesBean.get(context, bean, beanName));
}
}
@Component("nonAnnotatedComponent")
static class NonAnnotatedComponent {
}
@Component("annotatedComponent")
@ConfigurationProperties(prefix = "prefix")
static class AnnotatedComponent {
}
@ConfigurationProperties(prefix = "prefix")
static class AnnotatedBean {
}
static class NonAnnotatedBean {
}
static class NonAnnotatedGenericBean<T> {
}
@ConfigurationProperties
static class AnnotatedGenericBean<T> {
}
@Component("annotatedGenericComponent")
@ConfigurationProperties
static class AnnotatedGenericComponent<T> {
}
@Validated(BeanGroup.class)
@ConfigurationProperties
static class ValidatedBean {
}
@Configuration(proxyBeanMethods = false)
static class NonAnnotatedBeanConfiguration {
@Bean
@ConfigurationProperties(prefix = "prefix")
NonAnnotatedBean nonAnnotatedBean() {
return new NonAnnotatedBean();
}
}
@Configuration(proxyBeanMethods = false)
static class NonAnnotatedGenericBeanConfiguration {
@Bean
@ConfigurationProperties
NonAnnotatedGenericBean<String> nonAnnotatedGenericBean() {
return new NonAnnotatedGenericBean<>();
}
}
@Configuration(proxyBeanMethods = false)
static class AnnotatedGenericBeanConfiguration {
@Bean
AnnotatedGenericBean<String> annotatedGenericBean() {
return new AnnotatedGenericBean<>();
}
}
@Configuration(proxyBeanMethods = false)
static class AnnotatedBeanConfiguration {
@Bean
@ConfigurationProperties(prefix = "factory")
AnnotatedBean annotatedBean() {
return new AnnotatedBean();
}
}
@Configuration(proxyBeanMethods = false)
static class ValidatedBeanConfiguration {
@Bean
ValidatedBean validatedBean() {
return new ValidatedBean();
}
}
@Configuration(proxyBeanMethods = false)
static class ValidatedMethodConfiguration {
@Bean
@Validated(FactoryMethodGroup.class)
AnnotatedBean annotatedBean() {
return new AnnotatedBean();
}
}
@Configuration(proxyBeanMethods = false)
static class ValidatedMethodAndBeanConfiguration {
@Bean
@Validated(FactoryMethodGroup.class)
ValidatedBean validatedBean() {
return new ValidatedBean();
}
}
static class BeanGroup {
}
static class FactoryMethodGroup {
}
}
/*
* Copyright 2012-2019 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
*
* https://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.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertiesBindException}.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBindExceptionTests {
@Test
void createFromBeanHasDetails() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Example.class);
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.get(applicationContext,
applicationContext.getBean(Example.class), "example");
ConfigurationPropertiesBindException exception = new ConfigurationPropertiesBindException(bean,
new IllegalStateException());
assertThat(exception.getMessage()).isEqualTo("Error creating bean with name 'example': "
+ "Could not bind properties to 'ConfigurationPropertiesBindExceptionTests.Example' : "
+ "prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true; "
+ "nested exception is java.lang.IllegalStateException");
assertThat(exception.getBeanType()).isEqualTo(Example.class);
assertThat(exception.getBeanName()).isEqualTo("example");
assertThat(exception.getAnnotation()).isInstanceOf(ConfigurationProperties.class);
assertThat(exception.getCause()).isInstanceOf(IllegalStateException.class);
}
@Test
void createFromItemsHasDetails() {
Example example = new Example();
ConfigurationProperties annotation = example.getClass().getDeclaredAnnotation(ConfigurationProperties.class);
ConfigurationPropertiesBindException exception = new ConfigurationPropertiesBindException("example",
Example.class, annotation, new IllegalStateException());
assertThat(exception.getMessage()).isEqualTo("Error creating bean with name 'example': "
+ "Could not bind properties to 'ConfigurationPropertiesBindExceptionTests.Example' : "
+ "prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true; "
+ "nested exception is java.lang.IllegalStateException");
assertThat(exception.getBeanType()).isEqualTo(Example.class);
assertThat(exception.getBeanName()).isEqualTo("example");
assertThat(exception.getAnnotation()).isInstanceOf(ConfigurationProperties.class);
assertThat(exception.getCause()).isInstanceOf(IllegalStateException.class);
}
@Component("example")
@ConfigurationProperties
static class Example {
}
}
...@@ -17,7 +17,6 @@ package org.springframework.boot.context.properties; ...@@ -17,7 +17,6 @@ package org.springframework.boot.context.properties;
import java.io.IOException; import java.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
...@@ -39,14 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -39,14 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class ConfigurationPropertiesScanRegistrarTests { class ConfigurationPropertiesScanRegistrarTests {
private final ConfigurationPropertiesScanRegistrar registrar = new ConfigurationPropertiesScanRegistrar();
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@BeforeEach private final ConfigurationPropertiesScanRegistrar registrar = new ConfigurationPropertiesScanRegistrar(
void setup() { new MockEnvironment(), null);
this.registrar.setEnvironment(new MockEnvironment());
}
@Test @Test
void registerBeanDefintionsShouldScanForConfigurationProperties() throws IOException { void registerBeanDefintionsShouldScanForConfigurationProperties() throws IOException {
...@@ -60,7 +55,7 @@ class ConfigurationPropertiesScanRegistrarTests { ...@@ -60,7 +55,7 @@ class ConfigurationPropertiesScanRegistrarTests {
"bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties"); "bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties");
assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
} }
@Test @Test
...@@ -92,7 +87,7 @@ class ConfigurationPropertiesScanRegistrarTests { ...@@ -92,7 +87,7 @@ class ConfigurationPropertiesScanRegistrarTests {
"b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties"); "b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties");
assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
// Constructor injection // Constructor injection
assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class); assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
// Post-processing injection // Post-processing injection
assertThat(bSecondDefinition).isExactlyInstanceOf(GenericBeanDefinition.class); assertThat(bSecondDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
} }
......
...@@ -82,7 +82,7 @@ import org.springframework.validation.annotation.Validated; ...@@ -82,7 +82,7 @@ import org.springframework.validation.annotation.Validated;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.entry; import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
...@@ -216,7 +216,7 @@ class ConfigurationPropertiesTests { ...@@ -216,7 +216,7 @@ class ConfigurationPropertiesTests {
@Test @Test
void loadWhenBindingWithoutAndAnnotationShouldFail() { void loadWhenBindingWithoutAndAnnotationShouldFail() {
assertThatIllegalArgumentException().isThrownBy(() -> load(WithoutAndAnnotationConfiguration.class, "name:foo")) assertThatIllegalStateException().isThrownBy(() -> load(WithoutAndAnnotationConfiguration.class, "name:foo"))
.withMessageContaining("No ConfigurationProperties annotation found"); .withMessageContaining("No ConfigurationProperties annotation found");
} }
...@@ -776,6 +776,7 @@ class ConfigurationPropertiesTests { ...@@ -776,6 +776,7 @@ class ConfigurationPropertiesTests {
} }
@Test @Test
@SuppressWarnings("deprecation")
void loadWhenBindingOnBeanWithoutBeanDefinitionShouldBind() { void loadWhenBindingOnBeanWithoutBeanDefinitionShouldBind() {
load(BasicConfiguration.class, "name=test"); load(BasicConfiguration.class, "name=test");
BasicProperties bean = this.context.getBean(BasicProperties.class); BasicProperties bean = this.context.getBean(BasicProperties.class);
...@@ -1119,7 +1120,7 @@ class ConfigurationPropertiesTests { ...@@ -1119,7 +1120,7 @@ class ConfigurationPropertiesTests {
@EnableConfigurationProperties(WithCustomValidatorProperties.class) @EnableConfigurationProperties(WithCustomValidatorProperties.class)
static class WithCustomValidatorConfiguration { static class WithCustomValidatorConfiguration {
@Bean(name = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME) @Bean(name = EnableConfigurationProperties.VALIDATOR_BEAN_NAME)
CustomPropertiesValidator validator() { CustomPropertiesValidator validator() {
return new CustomPropertiesValidator(); return new CustomPropertiesValidator();
} }
...@@ -1130,7 +1131,7 @@ class ConfigurationPropertiesTests { ...@@ -1130,7 +1131,7 @@ class ConfigurationPropertiesTests {
@EnableConfigurationProperties(WithSetterThatThrowsValidationExceptionProperties.class) @EnableConfigurationProperties(WithSetterThatThrowsValidationExceptionProperties.class)
static class WithUnsupportedCustomValidatorConfiguration { static class WithUnsupportedCustomValidatorConfiguration {
@Bean(name = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME) @Bean(name = EnableConfigurationProperties.VALIDATOR_BEAN_NAME)
CustomPropertiesValidator validator() { CustomPropertiesValidator validator() {
return new CustomPropertiesValidator(); return new CustomPropertiesValidator();
} }
......
/*
* Copyright 2012-2019 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
*
* https://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.io.InputStream;
import java.io.OutputStream;
import org.junit.jupiter.api.Test;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConversionServiceDeducer}.
*
* @author Phillip Webb
*/
class ConversionServiceDeducerTests {
@Test
void getConversionServiceWhenHasConversionServiceBeanReturnsBean() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
CustomConverterServiceConfiguration.class);
ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext);
assertThat(deducer.getConversionService()).isInstanceOf(TestApplicationConversionService.class);
}
@Test
void getConversionServiceWhenHasNoConversionServiceBeanAndNoQualifiedBeansReturnsSharedInstance() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(EmptyConfiguration.class);
ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext);
assertThat(deducer.getConversionService()).isSameAs(ApplicationConversionService.getSharedInstance());
}
@Test
void getConversionServiceWhenHasQualifiedConverterBeansReturnsNewInstance() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
CustomConverterConfiguration.class);
ConversionServiceDeducer deducer = new ConversionServiceDeducer(applicationContext);
ConversionService conversionService = deducer.getConversionService();
assertThat(conversionService).isNotSameAs(ApplicationConversionService.getSharedInstance());
assertThat(conversionService.canConvert(InputStream.class, OutputStream.class)).isTrue();
}
@Configuration(proxyBeanMethods = false)
static class CustomConverterServiceConfiguration {
@Bean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)
TestApplicationConversionService conversionService() {
return new TestApplicationConversionService();
}
}
@Configuration(proxyBeanMethods = false)
static class EmptyConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class CustomConverterConfiguration {
@Bean
@ConfigurationPropertiesBinding
TestConveter testConveter() {
return new TestConveter();
}
}
private static class TestApplicationConversionService extends ApplicationConversionService {
}
private static class TestConveter implements Converter<InputStream, OutputStream> {
@Override
public OutputStream convert(InputStream source) {
throw new UnsupportedOperationException();
}
}
}
/*
* Copyright 2012-2019 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
*
* https://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.io.IOException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
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 EnableConfigurationPropertiesRegistrar}.
*
* @author Madhura Bhave
* @author Stephane Nicoll
*/
class EnableConfigurationPropertiesRegistrarTests {
private DefaultListableBeanFactory beanFactory;
private EnableConfigurationPropertiesRegistrar registrar;
@BeforeEach
void setup() {
this.beanFactory = spy(new DefaultListableBeanFactory());
this.registrar = new EnableConfigurationPropertiesRegistrar();
}
@Test
void typeWithDefaultConstructorShouldRegisterGenericBeanDefinition() throws Exception {
register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("foo-" + getClass().getName() + "$FooProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test
void typeWithOneConstructorWithParametersShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception {
register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("bar-" + getClass().getName() + "$BarProperties");
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
}
@Test
void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition() throws Exception {
register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("bing-" + getClass().getName() + "$BingProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test
void typeWithNoAnnotationShouldFail() {
assertThatIllegalStateException().isThrownBy(() -> register(InvalidConfiguration.class))
.withMessageContaining("No ConfigurationProperties annotation found")
.withMessageContaining(EnableConfigurationPropertiesRegistrar.class.getName());
}
@Test
void registrationWithDuplicatedTypeShouldRegisterSingleBeanDefinition() throws IOException {
register(DuplicateConfiguration.class);
String name = "foo-" + getClass().getName() + "$FooProperties";
verify(this.beanFactory, times(1)).registerBeanDefinition(eq(name), any());
}
@Test
void registrationWithNoTypeShouldNotRegisterAnything() throws IOException {
register(EmptyConfiguration.class);
String[] names = this.beanFactory.getBeanNamesForType(Object.class);
for (String name : names) {
assertThat(name).doesNotContain("-");
}
}
private void register(Class<?> configuration) {
AnnotationMetadata metadata = AnnotationMetadata.introspect(configuration);
this.registrar.registerBeanDefinitions(metadata, this.beanFactory);
}
@EnableConfigurationProperties({ FooProperties.class, BarProperties.class, BingProperties.class })
static class TestConfiguration {
}
@EnableConfigurationProperties(EnableConfigurationPropertiesRegistrarTests.class)
static class InvalidConfiguration {
}
@EnableConfigurationProperties({ FooProperties.class, FooProperties.class })
static class DuplicateConfiguration {
}
@EnableConfigurationProperties
static class EmptyConfiguration {
}
@ConfigurationProperties(prefix = "foo")
static class FooProperties {
}
@ConfigurationProperties(prefix = "bar")
static class BarProperties {
BarProperties(String foo) {
}
}
@ConfigurationProperties(prefix = "bing")
static class BingProperties {
BingProperties() {
}
BingProperties(String foo) {
}
}
}
/*
* Copyright 2012-2019 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
*
* https://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 org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySources;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertySourcesDeducer}.
*
* @author Phillip Webb
*/
class PropertySourcesDeducerTests {
@Test
void getPropertySourcesWhenHasSinglePropertySourcesPlaceholderConfigurerReturnsBean() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
PropertySourcesPlaceholderConfigurerConfiguration.class);
PropertySourcesDeducer deducer = new PropertySourcesDeducer(applicationContext);
PropertySources propertySources = deducer.getPropertySources();
assertThat(propertySources.get("test")).isInstanceOf(TestPropertySource.class);
}
@Test
void getPropertySourcesWhenHasNoPropertySourcesPlaceholderConfigurerReturnsEnvironmentSources() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(EmptyConfiguration.class);
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
environment.getPropertySources().addFirst(new TestPropertySource());
PropertySourcesDeducer deducer = new PropertySourcesDeducer(applicationContext);
PropertySources propertySources = deducer.getPropertySources();
assertThat(propertySources.get("test")).isInstanceOf(TestPropertySource.class);
}
@Test
void getPropertySourcesWhenHasMultiplePropertySourcesPlaceholderConfigurerReturnsEnvironmentSources() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(
MultiplePropertySourcesPlaceholderConfigurerConfiguration.class);
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
environment.getPropertySources().addFirst(new TestPropertySource());
PropertySourcesDeducer deducer = new PropertySourcesDeducer(applicationContext);
PropertySources propertySources = deducer.getPropertySources();
assertThat(propertySources.get("test")).isInstanceOf(TestPropertySource.class);
}
@Test
void getPropertySourcesWhenUnavailableThrowsException() {
ApplicationContext applicationContext = mock(ApplicationContext.class);
Environment environment = mock(Environment.class);
given(applicationContext.getEnvironment()).willReturn(environment);
PropertySourcesDeducer deducer = new PropertySourcesDeducer(applicationContext);
assertThatIllegalStateException().isThrownBy(() -> deducer.getPropertySources()).withMessage(
"Unable to obtain PropertySources from PropertySourcesPlaceholderConfigurer or Environment");
}
@Configuration(proxyBeanMethods = false)
static class PropertySourcesPlaceholderConfigurerConfiguration {
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new TestPropertySource());
configurer.setPropertySources(propertySources);
return configurer;
}
}
@Configuration(proxyBeanMethods = false)
static class EmptyConfiguration {
}
@Configuration(proxyBeanMethods = false)
static class MultiplePropertySourcesPlaceholderConfigurerConfiguration {
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer1() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer2() {
return new PropertySourcesPlaceholderConfigurer();
}
}
private static class TestPropertySource extends MapPropertySource {
TestPropertySource() {
super("test", Collections.emptyMap());
}
}
}
...@@ -15,14 +15,13 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory ...@@ -15,14 +15,13 @@ import org.springframework.core.type.classreading.SimpleMetadataReaderFactory
@Suppress("unused") @Suppress("unused")
class KotlinConfigurationPropertiesBeanRegistrarTests { class KotlinConfigurationPropertiesBeanRegistrarTests {
private val registrar = ConfigurationPropertiesBeanRegistrar()
private val beanFactory = DefaultListableBeanFactory() private val beanFactory = DefaultListableBeanFactory()
private val registrar = ConfigurationPropertiesBeanRegistrar(beanFactory)
@Test @Test
fun `type with default constructor should register generic bean definition`() { fun `type with default constructor should register generic bean definition`() {
this.registrar.registerBeanDefinitions( this.registrar.register(FooProperties::class.java)
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$FooProperties") "foo-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$FooProperties")
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java) assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
...@@ -30,33 +29,21 @@ class KotlinConfigurationPropertiesBeanRegistrarTests { ...@@ -30,33 +29,21 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
@Test @Test
fun `type with primary constructor and no autowired should register configuration properties bean definition`() { fun `type with primary constructor and no autowired should register configuration properties bean definition`() {
this.registrar.registerBeanDefinitions( this.registrar.register(BarProperties::class.java)
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties") "bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties")
assertThat(beanDefinition).isExactlyInstanceOf( assertThat(beanDefinition).isExactlyInstanceOf(
ConfigurationPropertiesBeanDefinition::class.java) ConfigurationPropertiesValueObjectBeanDefinition::class.java)
} }
@Test @Test
fun `type with no primary constructor should register generic bean definition`() { fun `type with no primary constructor should register generic bean definition`() {
this.registrar.registerBeanDefinitions( this.registrar.register(BingProperties::class.java)
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"bing-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BingProperties") "bing-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BingProperties")
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java) assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
} }
private fun getAnnotationMetadata(source: Class<*>): AnnotationMetadata {
return SimpleMetadataReaderFactory().getMetadataReader(source.name)
.annotationMetadata
}
@EnableConfigurationProperties(FooProperties::class, BarProperties::class,
BingProperties::class)
class TestConfiguration
@ConfigurationProperties(prefix = "foo") @ConfigurationProperties(prefix = "foo")
class FooProperties class FooProperties
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment