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 0b3015e4ff
- Add additional tests
Closes gh-16903
This commit is contained in:
@@ -49,8 +49,8 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
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.ConfigurationPropertiesBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -108,36 +108,17 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
|
||||
private ContextConfigurationProperties describeConfigurationProperties(ApplicationContext context,
|
||||
ObjectMapper mapper) {
|
||||
ConfigurationBeanFactoryMetadata beanFactoryMetadata = getBeanFactoryMetadata(context);
|
||||
Map<String, Object> beans = getConfigurationPropertiesBeans(context, beanFactoryMetadata);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> beanDescriptors = new HashMap<>();
|
||||
Map<String, ConfigurationPropertiesBean> beans = ConfigurationPropertiesBean.getAll(context);
|
||||
Map<String, ConfigurationPropertiesBeanDescriptor> descriptors = new HashMap<>();
|
||||
beans.forEach((beanName, bean) -> {
|
||||
String prefix = extractPrefix(context, beanFactoryMetadata, beanName);
|
||||
beanDescriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix,
|
||||
sanitize(prefix, safeSerialize(mapper, bean, prefix))));
|
||||
String prefix = bean.getAnnotation().prefix();
|
||||
descriptors.put(beanName, new ConfigurationPropertiesBeanDescriptor(prefix,
|
||||
sanitize(prefix, safeSerialize(mapper, bean.getInstance(), prefix))));
|
||||
});
|
||||
return new ContextConfigurationProperties(beanDescriptors,
|
||||
return new ContextConfigurationProperties(descriptors,
|
||||
(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
|
||||
* instead of throwing an exception if there is a problem).
|
||||
@@ -197,30 +178,6 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
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
|
||||
* information.
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.util.Map;
|
||||
import org.springframework.beans.BeansException;
|
||||
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.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -31,12 +33,14 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
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.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @since 1.1.0
|
||||
* @deprecated since 2.2.0 in favor of {@link ConfigurationPropertiesBean}
|
||||
*/
|
||||
@Deprecated
|
||||
public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
@@ -77,4 +81,13 @@ public class ConfigurationBeanFactoryMetadata implements ApplicationContextAware
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* {@link BeanDefinition} that is used for registering
|
||||
* {@link ConfigurationProperties @ConfigurationProperties} beans that are bound at
|
||||
* creation time.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
final class ConfigurationPropertiesBeanDefinition extends GenericBeanDefinition {
|
||||
|
||||
static ConfigurationPropertiesBeanDefinition from(ConfigurableListableBeanFactory beanFactory, String beanName,
|
||||
Class<?> type) {
|
||||
ConfigurationPropertiesBeanDefinition beanDefinition = new ConfigurationPropertiesBeanDefinition();
|
||||
beanDefinition.setBeanClass(type);
|
||||
beanDefinition.setInstanceSupplier(createBean(beanFactory, beanName, type));
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
private static <T> Supplier<T> createBean(ConfigurableListableBeanFactory beanFactory, String beanName,
|
||||
Class<T> type) {
|
||||
return () -> {
|
||||
ConfigurationProperties annotation = getAnnotation(type, ConfigurationProperties.class);
|
||||
Validated validated = getAnnotation(type, Validated.class);
|
||||
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) {
|
||||
return AnnotationUtils.findAnnotation(type, annotationType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* 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 @@
|
||||
* 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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
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.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@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 Christian Dupuis
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
final class ConfigurationPropertiesBeanRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
|
||||
getTypes(metadata).forEach(
|
||||
(type) -> ConfigurationPropertiesBeanDefinitionRegistrar.register(registry, beanFactory, type));
|
||||
private final BeanDefinitionRegistry registry;
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
|
||||
this.registry = registry;
|
||||
this.beanFactory = (BeanFactory) this.registry;
|
||||
}
|
||||
|
||||
private List<Class<?>> getTypes(AnnotationMetadata metadata) {
|
||||
MultiValueMap<String, Object> attributes = metadata
|
||||
.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
|
||||
return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
|
||||
void register(Class<?> type) {
|
||||
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
|
||||
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
|
||||
register(type, annotation);
|
||||
}
|
||||
|
||||
private List<Class<?>> collectClasses(List<?> values) {
|
||||
return values.stream().flatMap((value) -> Arrays.stream((Class<?>[]) value))
|
||||
.filter((type) -> void.class != type).collect(Collectors.toList());
|
||||
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;
|
||||
}
|
||||
|
||||
private void registerBeanDefinition(String beanName, Class<?> type,
|
||||
MergedAnnotation<ConfigurationProperties> annotation) {
|
||||
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
|
||||
+ " annotation found on '" + type.getName() + "'.");
|
||||
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
|
||||
}
|
||||
|
||||
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
|
||||
if (isValueObject(type)) {
|
||||
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
|
||||
}
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(type);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private boolean isValueObject(Class<?> type) {
|
||||
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type)) {
|
||||
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
|
||||
|
||||
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,
|
||||
Exception cause) {
|
||||
super(beanName, getMessage(beanType, annotation), cause);
|
||||
|
||||
@@ -23,6 +23,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
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.BindResult;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
@@ -45,43 +50,36 @@ import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* Internal class by the {@link ConfigurationPropertiesBindingPostProcessor} to handle the
|
||||
* actual {@link ConfigurationProperties @ConfigurationProperties} binding.
|
||||
* Internal class used by the {@link ConfigurationPropertiesBindingPostProcessor} to
|
||||
* handle the actual {@link ConfigurationProperties @ConfigurationProperties} binding.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConfigurationPropertiesBinder implements ApplicationContextAware {
|
||||
class ConfigurationPropertiesBinder {
|
||||
|
||||
/**
|
||||
* The bean name that this binder is registered with.
|
||||
*/
|
||||
static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";
|
||||
private static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";
|
||||
|
||||
private final String validatorBeanName;
|
||||
private static final String FACTORY_BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinderFactory";
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
|
||||
|
||||
private PropertySources propertySources;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private Validator configurationPropertiesValidator;
|
||||
private final PropertySources propertySources;
|
||||
|
||||
private boolean jsr303Present;
|
||||
private final Validator configurationPropertiesValidator;
|
||||
|
||||
private final boolean jsr303Present;
|
||||
|
||||
private volatile Validator jsr303Validator;
|
||||
|
||||
private volatile Binder binder;
|
||||
|
||||
ConfigurationPropertiesBinder(String validatorBeanName) {
|
||||
this.validatorBeanName = validatorBeanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
ConfigurationPropertiesBinder(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
|
||||
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext,
|
||||
this.validatorBeanName);
|
||||
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext);
|
||||
this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
|
||||
}
|
||||
|
||||
@@ -103,10 +101,9 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
|
||||
return annotation;
|
||||
}
|
||||
|
||||
private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext,
|
||||
String validatorBeanName) {
|
||||
if (applicationContext.containsBean(validatorBeanName)) {
|
||||
return applicationContext.getBean(validatorBeanName, Validator.class);
|
||||
private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) {
|
||||
if (applicationContext.containsBean(VALIDATOR_BEAN_NAME)) {
|
||||
return applicationContext.getBean(VALIDATOR_BEAN_NAME, Validator.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -183,4 +180,46 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
|
||||
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 @@
|
||||
|
||||
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.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
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.ApplicationContextAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
|
||||
@@ -56,16 +51,26 @@ public class ConfigurationPropertiesBindingPostProcessor
|
||||
/**
|
||||
* The bean name of the configuration properties validator.
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link ConfigurationPropertiesBindingPostProcessorRegistrar#VALIDATOR_BEAN_NAME}
|
||||
* {@link EnableConfigurationProperties#VALIDATOR_BEAN_NAME}
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String VALIDATOR_BEAN_NAME = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME;
|
||||
|
||||
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
|
||||
public static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
|
||||
|
||||
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
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
@@ -76,10 +81,8 @@ public class ConfigurationPropertiesBindingPostProcessor
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// We can't use constructor injection of the application context because
|
||||
// it causes eager factory bean initialization
|
||||
this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
|
||||
ConfigurationBeanFactoryMetadata.class);
|
||||
this.configurationPropertiesBinder = this.applicationContext.getBean(ConfigurationPropertiesBinder.BEAN_NAME,
|
||||
ConfigurationPropertiesBinder.class);
|
||||
this.registry = (BeanDefinitionRegistry) this.applicationContext.getAutowireCapableBeanFactory();
|
||||
this.binder = ConfigurationPropertiesBinder.get(this.applicationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,51 +92,39 @@ public class ConfigurationPropertiesBindingPostProcessor
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
|
||||
if (annotation != null && !hasBeenBound(beanName)) {
|
||||
bind(bean, beanName, annotation);
|
||||
ConfigurationPropertiesBean configurationPropertiesBean = ConfigurationPropertiesBean
|
||||
.get(this.applicationContext, bean, beanName);
|
||||
if (configurationPropertiesBean != null && !hasBoundValueObject(beanName)) {
|
||||
try {
|
||||
this.binder.bind(configurationPropertiesBean.asBindTarget());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ConfigurationPropertiesBindException(configurationPropertiesBean, ex);
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private boolean hasBeenBound(String beanName) {
|
||||
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) this.applicationContext
|
||||
.getAutowireCapableBeanFactory();
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
|
||||
return beanDefinition instanceof ConfigurationPropertiesBeanDefinition;
|
||||
}
|
||||
return false;
|
||||
private boolean hasBoundValueObject(String beanName) {
|
||||
return this.registry.containsBeanDefinition(beanName) && this.registry
|
||||
.getBeanDefinition(beanName) instanceof ConfigurationPropertiesValueObjectBeanDefinition;
|
||||
}
|
||||
|
||||
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
|
||||
ResolvableType type = getBeanType(bean, beanName);
|
||||
Validated validated = getAnnotation(bean, beanName, Validated.class);
|
||||
Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
|
||||
: new Annotation[] { annotation };
|
||||
Bindable<?> target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
|
||||
try {
|
||||
this.configurationPropertiesBinder.bind(target);
|
||||
/**
|
||||
* Register a {@link ConfigurationPropertiesBindingPostProcessor} bean if one is not
|
||||
* already registered.
|
||||
* @param registry the bean definition registry
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public static void register(BeanDefinitionRegistry registry) {
|
||||
Assert.notNull(registry, "Registry must not be null");
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME)) {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
|
||||
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
registry.registerBeanDefinition(BEAN_NAME, definition);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new ConfigurationPropertiesBindException(beanName, bean.getClass(), annotation, ex);
|
||||
}
|
||||
}
|
||||
|
||||
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 @@
|
||||
|
||||
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.GenericBeanDefinition;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
|
||||
@@ -29,45 +27,20 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @since 1.0.0
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link EnableConfigurationProperties @EnableConfigurationProperties}
|
||||
*/
|
||||
@Deprecated
|
||||
public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
/**
|
||||
* The bean name of the configuration properties validator.
|
||||
*/
|
||||
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME)) {
|
||||
registerConfigurationPropertiesBinder(registry);
|
||||
// Spring Cloud Function may call this with a null importingClassMetadata
|
||||
if (importingClassMetadata == null) {
|
||||
EnableConfigurationPropertiesRegistrar.registerInfrastructureBeans(registry);
|
||||
return;
|
||||
}
|
||||
if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
|
||||
registerConfigurationPropertiesBindingPostProcessor(registry);
|
||||
registerConfigurationBeanFactoryMetadata(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);
|
||||
new EnableConfigurationPropertiesRegistrar().registerBeanDefinitions(importingClassMetadata, registry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Configures the base packages used when scanning for
|
||||
@@ -30,6 +31,9 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* {@link #basePackageClasses()}, {@link #basePackages()} or its alias {@link #value()}
|
||||
* 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.
|
||||
* <p>
|
||||
* Note: Classes annotated or meta-annotated with {@link Component @Component} will not be
|
||||
* picked up by this annotation.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.2.0
|
||||
@@ -37,7 +41,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({ ConfigurationPropertiesScanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class })
|
||||
@Import({ ConfigurationPropertiesScanRegistrar.class })
|
||||
@EnableConfigurationProperties
|
||||
public @interface ConfigurationPropertiesScan {
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,12 +19,10 @@ import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
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.boot.context.TypeExcludeFilter;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
@@ -43,18 +41,23 @@ import org.springframework.util.StringUtils;
|
||||
* {@link ConfigurationProperties @ConfigurationProperties} bean definitions via scanning.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConfigurationPropertiesScanRegistrar
|
||||
implements ImportBeanDefinitionRegistrar, EnvironmentAware, ResourceLoaderAware {
|
||||
class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
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
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
|
||||
register(registry, (ConfigurableListableBeanFactory) registry, packagesToScan);
|
||||
scan(registry, packagesToScan);
|
||||
}
|
||||
|
||||
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
|
||||
@@ -69,43 +72,43 @@ class ConfigurationPropertiesScanRegistrar
|
||||
if (packagesToScan.isEmpty()) {
|
||||
packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName()));
|
||||
}
|
||||
packagesToScan.removeIf((candidate) -> !StringUtils.hasText(candidate));
|
||||
return packagesToScan;
|
||||
}
|
||||
|
||||
protected void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
|
||||
Set<String> packagesToScan) {
|
||||
scan(packagesToScan, beanFactory, registry);
|
||||
private void scan(BeanDefinitionRegistry registry, Set<String> packages) {
|
||||
ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(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,
|
||||
BeanDefinitionRegistry registry) {
|
||||
private ClassPathScanningCandidateComponentProvider getScanner(BeanDefinitionRegistry registry) {
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.setEnvironment(this.environment);
|
||||
scanner.setResourceLoader(this.resourceLoader);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigurationProperties.class));
|
||||
TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter();
|
||||
typeExcludeFilter.setBeanFactory(beanFactory);
|
||||
typeExcludeFilter.setBeanFactory((BeanFactory) registry);
|
||||
scanner.addExcludeFilter(typeExcludeFilter);
|
||||
for (String basePackage : packages) {
|
||||
if (StringUtils.hasText(basePackage)) {
|
||||
scan(beanFactory, registry, scanner, basePackage);
|
||||
}
|
||||
return scanner;
|
||||
}
|
||||
|
||||
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,
|
||||
ClassPathScanningCandidateComponentProvider scanner, String basePackage) throws LinkageError {
|
||||
for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) {
|
||||
String beanClassName = candidate.getBeanClassName();
|
||||
try {
|
||||
Class<?> type = ClassUtils.forName(beanClassName, null);
|
||||
if (!isComponent(type)) {
|
||||
ConfigurationPropertiesBeanDefinitionRegistrar.register(registry, beanFactory, type);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Ignore
|
||||
}
|
||||
private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
|
||||
if (!isComponent(type)) {
|
||||
registrar.register(type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,14 +116,4 @@ class ConfigurationPropertiesScanRegistrar
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* {@link BeanDefinition} that is used for registering
|
||||
* {@link ConfigurationProperties @ConfigurationProperties} value object beans that are
|
||||
* bound at creation time.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
final class ConfigurationPropertiesValueObjectBeanDefinition extends GenericBeanDefinition {
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final String beanName;
|
||||
|
||||
ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.beanName = beanName;
|
||||
setBeanClass(beanClass);
|
||||
setInstanceSupplier(this::createBean);
|
||||
}
|
||||
|
||||
private Object createBean() {
|
||||
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;
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Import({ ConfigurationPropertiesBeanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class })
|
||||
@Import(EnableConfigurationPropertiesRegistrar.class)
|
||||
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
|
||||
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to deduce the {@link PropertySources} to use for configuration binding.
|
||||
@@ -49,19 +50,9 @@ class PropertySourcesDeducer {
|
||||
return configurer.getAppliedPropertySources();
|
||||
}
|
||||
MutablePropertySources sources = extractEnvironmentPropertySources();
|
||||
if (sources != null) {
|
||||
return sources;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
Assert.state(sources != null,
|
||||
"Unable to obtain PropertySources from PropertySourcesPlaceholderConfigurer or Environment");
|
||||
}
|
||||
|
||||
private MutablePropertySources extractEnvironmentPropertySources() {
|
||||
Environment environment = this.applicationContext.getEnvironment();
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
return ((ConfigurableEnvironment) environment).getPropertySources();
|
||||
}
|
||||
return null;
|
||||
return sources;
|
||||
}
|
||||
|
||||
private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() {
|
||||
@@ -78,4 +69,12 @@ class PropertySourcesDeducer {
|
||||
return null;
|
||||
}
|
||||
|
||||
private MutablePropertySources extractEnvironmentPropertySources() {
|
||||
Environment environment = this.applicationContext.getEnvironment();
|
||||
if (environment instanceof ConfigurableEnvironment) {
|
||||
return ((ConfigurableEnvironment) environment).getPropertySources();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,130 +13,98 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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.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.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.assertThatIllegalArgumentException;
|
||||
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;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationPropertiesBeanRegistrar}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
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);
|
||||
void registerWhenNotAlreadyRegisteredAddBeanDefinition() {
|
||||
String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
|
||||
this.registrar.register(BeanConfigurationProperties.class);
|
||||
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
|
||||
assertThat(definition).isNotNull();
|
||||
assertThat(definition.getBeanClassName()).isEqualTo(BeanConfigurationProperties.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeWithOneConstructorWithParametersShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception {
|
||||
this.registrar.registerBeanDefinitions(getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bar-org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrarTests$BarProperties");
|
||||
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
|
||||
void registerWhenAlreadyContainsNameDoesNotReplace() {
|
||||
String beanName = "beancp-" + BeanConfigurationProperties.class.getName();
|
||||
this.registry.registerBeanDefinition(beanName, new GenericBeanDefinition());
|
||||
this.registrar.register(BeanConfigurationProperties.class);
|
||||
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
|
||||
assertThat(definition).isNotNull();
|
||||
assertThat(definition.getBeanClassName()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition() throws Exception {
|
||||
this.registrar.registerBeanDefinitions(getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
|
||||
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bing-org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrarTests$BingProperties");
|
||||
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
void registerWhenNoAnnotationThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.registrar.register(NoAnnotationConfigurationProperties.class))
|
||||
.withMessageContaining("No ConfigurationProperties annotation found");
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeWithNoAnnotationShouldFail() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.registrar
|
||||
.registerBeanDefinitions(getAnnotationMetadata(InvalidConfiguration.class), this.beanFactory))
|
||||
.withMessageContaining("No ConfigurationProperties annotation found")
|
||||
.withMessageContaining(ConfigurationPropertiesBeanRegistrar.class.getName());
|
||||
void registerWhenValueObjectRegistersValueObjectBeanDefinition() {
|
||||
String beanName = "valuecp-" + ValueObjectConfigurationProperties.class.getName();
|
||||
this.registrar.register(ValueObjectConfigurationProperties.class);
|
||||
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
|
||||
assertThat(definition).isInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationWithDuplicatedTypeShouldRegisterSingleBeanDefinition() throws IOException {
|
||||
DefaultListableBeanFactory factory = spy(this.beanFactory);
|
||||
this.registrar.registerBeanDefinitions(getAnnotationMetadata(DuplicateConfiguration.class), factory);
|
||||
verify(factory, times(1)).registerBeanDefinition(anyString(), any());
|
||||
void registerWhenNotValueObjectRegistersGenericBeanDefinition() {
|
||||
String beanName = MultiConstructorBeanConfigurationProperties.class.getName();
|
||||
this.registrar.register(MultiConstructorBeanConfigurationProperties.class);
|
||||
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
|
||||
assertThat(definition).isInstanceOf(GenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registrationWithNoTypeShouldNotRegisterAnything() throws IOException {
|
||||
DefaultListableBeanFactory factory = spy(this.beanFactory);
|
||||
this.registrar.registerBeanDefinitions(getAnnotationMetadata(EmptyConfiguration.class), factory);
|
||||
verifyZeroInteractions(factory);
|
||||
}
|
||||
|
||||
private AnnotationMetadata getAnnotationMetadata(Class<?> source) throws IOException {
|
||||
return new SimpleMetadataReaderFactory().getMetadataReader(source.getName()).getAnnotationMetadata();
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties({ FooProperties.class, BarProperties.class, BingProperties.class })
|
||||
static class TestConfiguration {
|
||||
@ConfigurationProperties(prefix = "beancp")
|
||||
static class BeanConfigurationProperties {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(ConfigurationPropertiesBeanRegistrarTests.class)
|
||||
static class InvalidConfiguration {
|
||||
static class NoAnnotationConfigurationProperties {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties({ FooProperties.class, FooProperties.class })
|
||||
static class DuplicateConfiguration {
|
||||
@ConfigurationProperties(prefix = "valuecp")
|
||||
static class ValueObjectConfigurationProperties {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties
|
||||
static class EmptyConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "foo")
|
||||
static class FooProperties {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bar")
|
||||
static class BarProperties {
|
||||
|
||||
BarProperties(String foo) {
|
||||
ValueObjectConfigurationProperties(String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bing")
|
||||
static class BingProperties {
|
||||
@ConfigurationProperties
|
||||
static class MultiConstructorBeanConfigurationProperties {
|
||||
|
||||
BingProperties() {
|
||||
MultiConstructorBeanConfigurationProperties() {
|
||||
}
|
||||
|
||||
BingProperties(String foo) {
|
||||
MultiConstructorBeanConfigurationProperties(String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -39,14 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
class ConfigurationPropertiesScanRegistrarTests {
|
||||
|
||||
private final ConfigurationPropertiesScanRegistrar registrar = new ConfigurationPropertiesScanRegistrar();
|
||||
|
||||
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.registrar.setEnvironment(new MockEnvironment());
|
||||
}
|
||||
private final ConfigurationPropertiesScanRegistrar registrar = new ConfigurationPropertiesScanRegistrar(
|
||||
new MockEnvironment(), null);
|
||||
|
||||
@Test
|
||||
void registerBeanDefintionsShouldScanForConfigurationProperties() throws IOException {
|
||||
@@ -60,7 +55,7 @@ class ConfigurationPropertiesScanRegistrarTests {
|
||||
"bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties");
|
||||
assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
|
||||
assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,7 +87,7 @@ class ConfigurationPropertiesScanRegistrarTests {
|
||||
"b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties");
|
||||
assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
// Constructor injection
|
||||
assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
|
||||
assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
|
||||
// Post-processing injection
|
||||
assertThat(bSecondDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
|
||||
}
|
||||
|
||||
@@ -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.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.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -216,7 +216,7 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
@Test
|
||||
void loadWhenBindingWithoutAndAnnotationShouldFail() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> load(WithoutAndAnnotationConfiguration.class, "name:foo"))
|
||||
assertThatIllegalStateException().isThrownBy(() -> load(WithoutAndAnnotationConfiguration.class, "name:foo"))
|
||||
.withMessageContaining("No ConfigurationProperties annotation found");
|
||||
}
|
||||
|
||||
@@ -776,6 +776,7 @@ class ConfigurationPropertiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void loadWhenBindingOnBeanWithoutBeanDefinitionShouldBind() {
|
||||
load(BasicConfiguration.class, "name=test");
|
||||
BasicProperties bean = this.context.getBean(BasicProperties.class);
|
||||
@@ -1119,7 +1120,7 @@ class ConfigurationPropertiesTests {
|
||||
@EnableConfigurationProperties(WithCustomValidatorProperties.class)
|
||||
static class WithCustomValidatorConfiguration {
|
||||
|
||||
@Bean(name = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME)
|
||||
@Bean(name = EnableConfigurationProperties.VALIDATOR_BEAN_NAME)
|
||||
CustomPropertiesValidator validator() {
|
||||
return new CustomPropertiesValidator();
|
||||
}
|
||||
@@ -1130,7 +1131,7 @@ class ConfigurationPropertiesTests {
|
||||
@EnableConfigurationProperties(WithSetterThatThrowsValidationExceptionProperties.class)
|
||||
static class WithUnsupportedCustomValidatorConfiguration {
|
||||
|
||||
@Bean(name = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME)
|
||||
@Bean(name = EnableConfigurationProperties.VALIDATOR_BEAN_NAME)
|
||||
CustomPropertiesValidator validator() {
|
||||
return new CustomPropertiesValidator();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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
|
||||
@Suppress("unused")
|
||||
class KotlinConfigurationPropertiesBeanRegistrarTests {
|
||||
|
||||
private val registrar = ConfigurationPropertiesBeanRegistrar()
|
||||
|
||||
private val beanFactory = DefaultListableBeanFactory()
|
||||
|
||||
private val registrar = ConfigurationPropertiesBeanRegistrar(beanFactory)
|
||||
|
||||
@Test
|
||||
fun `type with default constructor should register generic bean definition`() {
|
||||
this.registrar.registerBeanDefinitions(
|
||||
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
|
||||
this.registrar.register(FooProperties::class.java)
|
||||
val beanDefinition = this.beanFactory.getBeanDefinition(
|
||||
"foo-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$FooProperties")
|
||||
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
|
||||
@@ -30,33 +29,21 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
|
||||
|
||||
@Test
|
||||
fun `type with primary constructor and no autowired should register configuration properties bean definition`() {
|
||||
this.registrar.registerBeanDefinitions(
|
||||
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
|
||||
this.registrar.register(BarProperties::class.java)
|
||||
val beanDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties")
|
||||
assertThat(beanDefinition).isExactlyInstanceOf(
|
||||
ConfigurationPropertiesBeanDefinition::class.java)
|
||||
ConfigurationPropertiesValueObjectBeanDefinition::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with no primary constructor should register generic bean definition`() {
|
||||
this.registrar.registerBeanDefinitions(
|
||||
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
|
||||
this.registrar.register(BingProperties::class.java)
|
||||
val beanDefinition = this.beanFactory.getBeanDefinition(
|
||||
"bing-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BingProperties")
|
||||
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")
|
||||
class FooProperties
|
||||
|
||||
|
||||
Reference in New Issue
Block a user