Add @ImportConfigurationPropertiesBean support

Add repeatable `@ImportConfigurationPropertiesBean` annotation that can
be used to import types and treat them as `@ConfigurationProperties`
beans. This annotation is specifically designed to support third-party
classes that can't contain any Spring annotations.

Closes gh-23172
This commit is contained in:
Phillip Webb
2020-09-08 16:16:40 -07:00
parent d2e67ab84d
commit f260c77fe3
35 changed files with 1272 additions and 133 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.context.properties;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -75,7 +76,7 @@ public final class ConfigurationPropertiesBean {
this.instance = instance;
this.annotation = annotation;
this.bindTarget = bindTarget;
this.bindMethod = BindMethod.forType(bindTarget.getType().resolve());
this.bindMethod = BindMethod.forBindable(bindTarget);
}
/**
@@ -175,7 +176,8 @@ public final class ConfigurationPropertiesBean {
if (beanFactory.findAnnotationOnBean(beanName, ConfigurationProperties.class) != null) {
return true;
}
Method factoryMethod = findFactoryMethod(beanFactory, beanName);
BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName);
Method factoryMethod = findFactoryMethod(beanFactory, beanDefinition);
return findMergedAnnotation(factoryMethod, ConfigurationProperties.class).isPresent();
}
catch (NoSuchBeanDefinitionException ex) {
@@ -197,33 +199,41 @@ public final class ConfigurationPropertiesBean {
* {@link ConfigurationProperties @ConfigurationProperties}
*/
public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) {
Method factoryMethod = findFactoryMethod(applicationContext, beanName);
return create(beanName, bean, bean.getClass(), factoryMethod);
ConfigurableListableBeanFactory beanFactory = getBeanFactory(applicationContext);
BeanDefinition beanDefinition = findBeanDefinition(beanFactory, beanName);
Method factoryMethod = findFactoryMethod(beanFactory, beanDefinition);
ConfigurationProperties annotation = findAnnotation(beanDefinition);
boolean deduceBindConstructor = (beanDefinition instanceof ConfigurationPropertiesValueObjectBeanDefinition)
? ((ConfigurationPropertiesValueObjectBeanDefinition) beanDefinition).isDeduceBindConstructor() : false;
return create(beanName, bean, bean.getClass(), factoryMethod, annotation, deduceBindConstructor);
}
private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
private static ConfigurableListableBeanFactory getBeanFactory(ApplicationContext applicationContext) {
if (applicationContext instanceof ConfigurableApplicationContext) {
return findFactoryMethod((ConfigurableApplicationContext) applicationContext, beanName);
return ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
}
return null;
}
private static Method findFactoryMethod(ConfigurableApplicationContext applicationContext, String beanName) {
return findFactoryMethod(applicationContext.getBeanFactory(), beanName);
private static BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
if (beanFactory != null && beanFactory.containsBeanDefinition(beanName)) {
return beanFactory.getMergedBeanDefinition(beanName);
}
return null;
}
private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, String beanName) {
if (beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
if (beanDefinition instanceof RootBeanDefinition) {
Method resolvedFactoryMethod = ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod();
if (resolvedFactoryMethod != null) {
return resolvedFactoryMethod;
}
private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory,
BeanDefinition beanDefinition) {
if (beanFactory == null || beanDefinition == null) {
return null;
}
if (beanDefinition instanceof RootBeanDefinition) {
Method resolvedFactoryMethod = ((RootBeanDefinition) beanDefinition).getResolvedFactoryMethod();
if (resolvedFactoryMethod != null) {
return resolvedFactoryMethod;
}
return findFactoryMethodUsingReflection(beanFactory, beanDefinition);
}
return null;
return findFactoryMethodUsingReflection(beanFactory, beanDefinition);
}
private static Method findFactoryMethodUsingReflection(ConfigurableListableBeanFactory beanFactory,
@@ -246,15 +256,19 @@ public final class ConfigurationPropertiesBean {
return factoryMethod.get();
}
static ConfigurationPropertiesBean forValueObject(Class<?> beanClass, String beanName) {
ConfigurationPropertiesBean propertiesBean = create(beanName, null, beanClass, null);
static ConfigurationPropertiesBean forValueObject(Class<?> beanClass, String beanName,
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
ConfigurationPropertiesBean propertiesBean = create(beanName, null, beanClass, null,
annotation.isPresent() ? annotation.synthesize() : null, deduceBindConstructor);
Assert.state(propertiesBean != null && propertiesBean.getBindMethod() == BindMethod.VALUE_OBJECT,
() -> "Bean '" + beanName + "' is not a @ConfigurationProperties value object");
return propertiesBean;
}
private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory) {
ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class);
private static ConfigurationPropertiesBean create(String name, Object instance, Class<?> type, Method factory,
ConfigurationProperties annotation, boolean deduceBindConstructor) {
annotation = (annotation != null) ? annotation
: findAnnotation(instance, type, factory, ConfigurationProperties.class);
if (annotation == null) {
return null;
}
@@ -267,9 +281,19 @@ public final class ConfigurationPropertiesBean {
if (instance != null) {
bindTarget = bindTarget.withExistingValue(instance);
}
if (deduceBindConstructor) {
bindTarget = bindTarget.withAttribute(
ConfigurationPropertiesBindConstructorProvider.DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE, true);
}
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
}
private static ConfigurationProperties findAnnotation(BeanDefinition beanDefinition) {
MergedAnnotation<ConfigurationProperties> annotation = ConfigurationPropertiesBeanDefinition
.getAnnotation(beanDefinition);
return (annotation.isPresent()) ? annotation.synthesize() : null;
}
private static <A extends Annotation> A findAnnotation(Object instance, Class<?> type, Method factory,
Class<A> annotationType) {
MergedAnnotation<A> annotation = MergedAnnotation.missing();
@@ -308,8 +332,27 @@ public final class ConfigurationPropertiesBean {
VALUE_OBJECT;
static BindMethod forType(Class<?> type) {
return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null)
? VALUE_OBJECT : JAVA_BEAN;
return forType(type, false);
}
static BindMethod forType(Class<?> type, boolean deduceBindConstructor) {
Constructor<?> constructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(type, deduceBindConstructor, false);
if (deduceBindConstructor) {
Assert.state(constructor != null,
() -> "Unable to deduce @ConfigurationProperties bind method for " + type.getName());
}
return hasParameters(constructor) ? VALUE_OBJECT : JAVA_BEAN;
}
static BindMethod forBindable(Bindable<?> bindable) {
Constructor<?> constructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(bindable, false);
return hasParameters(constructor) ? VALUE_OBJECT : JAVA_BEAN;
}
private static boolean hasParameters(Constructor<?> constructor) {
return constructor != null && constructor.getParameterCount() > 0;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2020 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.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.Conventions;
import org.springframework.core.annotation.MergedAnnotation;
/**
* {@link BeanDefinition} that is used for registering
* {@link ConfigurationProperties @ConfigurationProperties} beans.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBeanDefinition extends GenericBeanDefinition {
private static final String ANNOTATION_ATTRIBUTE = Conventions
.getQualifiedAttributeName(ConfigurationPropertiesBeanDefinition.class, "annotation");
ConfigurationPropertiesBeanDefinition(Class<?> beanClass, MergedAnnotation<ConfigurationProperties> annotation) {
setBeanClass(beanClass);
setAttribute(ANNOTATION_ATTRIBUTE, annotation);
}
@SuppressWarnings("unchecked")
static MergedAnnotation<ConfigurationProperties> getAnnotation(BeanDefinition beanDefinition) {
MergedAnnotation<ConfigurationProperties> annotation = (beanDefinition != null)
? (MergedAnnotation<ConfigurationProperties>) beanDefinition.getAttribute(ANNOTATION_ATTRIBUTE) : null;
return (annotation != null) ? annotation : MergedAnnotation.missing();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -20,7 +20,6 @@ import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
@@ -54,9 +53,13 @@ final class ConfigurationPropertiesBeanRegistrar {
}
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
register(type, annotation, false);
}
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
String name = getName(type, annotation);
if (!containsBeanDefinition(name)) {
registerBeanDefinition(name, type, annotation);
registerBeanDefinition(name, type, annotation, deduceBindConstructor);
}
}
@@ -81,19 +84,20 @@ final class ConfigurationPropertiesBeanRegistrar {
}
private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
this.registry.registerBeanDefinition(beanName,
createBeanDefinition(beanName, type, annotation, deduceBindConstructor));
}
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
private BeanDefinition createBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
if (BindMethod.forType(type, deduceBindConstructor) == BindMethod.VALUE_OBJECT) {
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type, annotation,
deduceBindConstructor);
}
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
return new ConfigurationPropertiesBeanDefinition(type, annotation);
}
}

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.bind.BindConstructorProvider;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.core.Conventions;
import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.Assert;
@@ -36,19 +37,35 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
static final String DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE = Conventions
.getQualifiedAttributeName(ConfigurationPropertiesBindConstructorProvider.class, "deduceBindConstructor");
@Override
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding);
Boolean deduceBindConstructor = (Boolean) bindable.getAttribute(DEDUCE_BIND_CONSTRUCTOR_ATTRIUBTE);
return getBindConstructor(bindable.getType().resolve(), Boolean.TRUE.equals(deduceBindConstructor),
isNestedConstructorBinding);
}
Constructor<?> getBindConstructor(Class<?> type, boolean isNestedConstructorBinding) {
Constructor<?> getBindConstructor(Class<?> type, boolean deduceBindConstructor,
boolean isNestedConstructorBinding) {
if (type == null) {
return null;
}
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
if (constructor == null && (isConstructorBindingAnnotatedType(type) || isNestedConstructorBinding)) {
if (constructor != null) {
return constructor;
}
boolean isConstructorBindingAnnotatedType = isConstructorBindingAnnotatedType(type);
if (deduceBindConstructor || isNestedConstructorBinding || isConstructorBindingAnnotatedType) {
constructor = deduceBindConstructor(type);
}
if (deduceBindConstructor && isConstructorBindingAnnotatedType && !isNestedConstructorBinding) {
Assert.state(constructor != null,
() -> "Unable to deduce constructor for @ConstructorBinding class " + type.getName());
Assert.state(constructor.getParameterCount() > 0,
() -> "Deduced no-args constructor for @ConstructorBinding class " + type.getName());
}
return constructor;
}
@@ -86,7 +103,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
return deducedKotlinBindConstructor(type);
}
Constructor<?>[] constructors = type.getDeclaredConstructors();
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
if (constructors.length == 1) {
return constructors[0];
}
return null;
@@ -94,7 +111,7 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
private Constructor<?> deducedKotlinBindConstructor(Class<?> type) {
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) {
if (primaryConstructor != null) {
return primaryConstructor;
}
return null;

View File

@@ -25,6 +25,7 @@ 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.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -206,7 +207,14 @@ class ConfigurationPropertiesBinder {
}
static ConfigurationPropertiesBinder get(BeanFactory beanFactory) {
return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
try {
return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
}
catch (NoSuchBeanDefinitionException ex) {
throw new NoSuchBeanDefinitionException(ex.getBeanName(),
"Unable to find ConfigurationPropertiesBinder bean '" + BEAN_NAME
+ "', ensure @EnableConfigurationProperties has been specified");
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -18,7 +18,7 @@ package org.springframework.boot.context.properties;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation;
/**
* {@link BeanDefinition} that is used for registering
@@ -29,21 +29,30 @@ import org.springframework.beans.factory.support.GenericBeanDefinition;
* @author Madhura Bhave
* @author Phillip Webb
*/
final class ConfigurationPropertiesValueObjectBeanDefinition extends GenericBeanDefinition {
final class ConfigurationPropertiesValueObjectBeanDefinition extends ConfigurationPropertiesBeanDefinition {
private final BeanFactory beanFactory;
private final String beanName;
ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass) {
private final boolean deduceBindConstructor;
ConfigurationPropertiesValueObjectBeanDefinition(BeanFactory beanFactory, String beanName, Class<?> beanClass,
MergedAnnotation<ConfigurationProperties> annotation, boolean deduceBindConstructor) {
super(beanClass, annotation);
this.beanFactory = beanFactory;
this.beanName = beanName;
setBeanClass(beanClass);
this.deduceBindConstructor = deduceBindConstructor;
setInstanceSupplier(this::createBean);
}
boolean isDeduceBindConstructor() {
return this.deduceBindConstructor;
}
private Object createBean() {
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(getBeanClass(), this.beanName);
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(getBeanClass(), this.beanName,
getAnnotation(this), this.deduceBindConstructor);
ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
try {
return binder.bindOrCreate(bean);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -51,6 +51,7 @@ public @interface EnableConfigurationProperties {
* {@link ConfigurationProperties @ConfigurationProperties} annotated beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value.
* @return {@code @ConfigurationProperties} annotated beans to register
* @see ImportConfigurationPropertiesBean
*/
Class<?>[] value() default {};

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2012-2020 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.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
/**
* Indicates one or more {@link ConfigurationProperties @ConfigurationProperties} classes
* to import as Spring Beans. Typically used on {@link Configuration @Configuration}
* classes to expose third-party classes as configuration property beans.
* <p>
* Classes imported via this annotation that have a default constructor will use
* {@code setter} binding, those with a non-default constructor will use
* {@link ConstructorBinding @ConstructorBinding}. If you are looking to inject beans into
* a constructor, you should use a regular {@link Configuration @Configuration} class
* {@code @Bean} method instead.
*
* @author Phillip Webb
* @since 2.4.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableConfigurationProperties
@ConfigurationProperties
@Repeatable(ImportConfigurationPropertiesBeans.class)
@Import(ImportConfigurationPropertiesBeanRegistrar.class)
public @interface ImportConfigurationPropertiesBean {
/**
* One or more types that should be imported as a bean.
* @return the types to import
*/
Class<?>[] type();
/**
* The prefix of the properties that are valid to bind to this object. A valid prefix
* is defined by one or more words separated with dots (e.g.
* {@code "acme.system.feature"}).
* @return the prefix of the properties to bind
* @see ConfigurationProperties#prefix()
*/
@AliasFor(annotation = ConfigurationProperties.class)
String prefix() default "";
/**
* Flag to indicate that when binding to this object invalid fields should be ignored.
* Invalid means invalid according to the binder that is used, and usually this means
* fields of the wrong type (or that cannot be coerced into the correct type).
* @return the flag value (default false)
* @see ConfigurationProperties#ignoreInvalidFields()
*/
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreInvalidFields() default false;
/**
* Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties.
* @return the flag value (default true)
* @see ConfigurationProperties#ignoreUnknownFields()
*/
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreUnknownFields() default true;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2012-2020 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.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.type.AnnotationMetadata;
/**
* {@link ImportBeanDefinitionRegistrar} for
* {@link ImportConfigurationPropertiesBean @ImportConfigurationPropertiesBean}.
*
* @author Phillip Webb
*/
class ImportConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
ConfigurationPropertiesBeanRegistrar registrar = new ConfigurationPropertiesBeanRegistrar(registry);
MergedAnnotations annotations = importingClassMetadata.getAnnotations();
registerBeans(registrar, annotations.get(ImportConfigurationPropertiesBeans.class));
registerBean(registrar, annotations.get(ImportConfigurationPropertiesBean.class));
}
private void registerBeans(ConfigurationPropertiesBeanRegistrar registrar,
MergedAnnotation<ImportConfigurationPropertiesBeans> annotation) {
if (!annotation.isPresent()) {
return;
}
for (MergedAnnotation<ImportConfigurationPropertiesBean> containedAnnotation : annotation
.getAnnotationArray(MergedAnnotation.VALUE, ImportConfigurationPropertiesBean.class)) {
registerBean(registrar, containedAnnotation);
}
}
private void registerBean(ConfigurationPropertiesBeanRegistrar registrar,
MergedAnnotation<ImportConfigurationPropertiesBean> annotation) {
if (!annotation.isPresent()) {
return;
}
Class<?>[] types = annotation.getClassArray("type");
MergedAnnotation<ConfigurationProperties> configurationPropertiesAnnotation = MergedAnnotations
.from(annotation.synthesize()).get(ConfigurationProperties.class);
for (Class<?> type : types) {
registrar.register(type, configurationPropertiesAnnotation, true);
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2020 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.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Container annotation that aggregates several {@link ImportConfigurationPropertiesBean}
* annotations.
*
* @author Phillip Webb
* @since 2.4.0
* @see ImportConfigurationPropertiesBean
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableConfigurationProperties
@Import(ImportConfigurationPropertiesBeanRegistrar.class)
public @interface ImportConfigurationPropertiesBeans {
/**
* The contained {@link ImportConfigurationPropertiesBean} annotations.
* @return the contained annotations
*/
ImportConfigurationPropertiesBean[] value();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.bind;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -42,6 +44,8 @@ public final class Bindable<T> {
private static final Annotation[] NO_ANNOTATIONS = {};
private static final Map<String, Object> NO_ATTRIBUTES = Collections.emptyMap();
private final ResolvableType type;
private final ResolvableType boxedType;
@@ -50,11 +54,15 @@ public final class Bindable<T> {
private final Annotation[] annotations;
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations) {
private final Map<String, Object> attributes;
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations,
Map<String, Object> attributes) {
this.type = type;
this.boxedType = boxedType;
this.value = value;
this.annotations = annotations;
this.attributes = attributes;
}
/**
@@ -105,6 +113,16 @@ public final class Bindable<T> {
return null;
}
/**
* Return the value of an attribute that has been associated with this
* {@link Bindable}.
* @param name the attribute name
* @return the associated attribute value or {@code null}
*/
public Object getAttribute(String name) {
return this.attributes.get(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -117,6 +135,7 @@ public final class Bindable<T> {
boolean result = true;
result = result && nullSafeEquals(this.type.resolve(), other.type.resolve());
result = result && nullSafeEquals(this.annotations, other.annotations);
result = result && nullSafeEquals(this.attributes, other.attributes);
return result;
}
@@ -126,6 +145,7 @@ public final class Bindable<T> {
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
result = prime * result + ObjectUtils.nullSafeHashCode(this.attributes);
return result;
}
@@ -135,6 +155,7 @@ public final class Bindable<T> {
creator.append("type", this.type);
creator.append("value", (this.value != null) ? "provided" : "none");
creator.append("annotations", this.annotations);
creator.append("attributes", this.attributes);
return creator.toString();
}
@@ -149,7 +170,19 @@ public final class Bindable<T> {
*/
public Bindable<T> withAnnotations(Annotation... annotations) {
return new Bindable<>(this.type, this.boxedType, this.value,
(annotations != null) ? annotations : NO_ANNOTATIONS);
(annotations != null) ? annotations : NO_ANNOTATIONS, this.attributes);
}
/**
* Create an updated {@link Bindable} instance with the specified attribute.
* @param name the attribute name
* @param value the attribute value
* @return an updated {@link Bindable}
*/
public Bindable<T> withAttribute(String name, Object value) {
Map<String, Object> attributes = new HashMap<>(this.attributes);
attributes.put(name, value);
return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, attributes);
}
/**
@@ -162,7 +195,7 @@ public final class Bindable<T> {
existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue),
() -> "ExistingValue must be an instance of " + this.type);
Supplier<T> value = (existingValue != null) ? () -> existingValue : null;
return new Bindable<>(this.type, this.boxedType, value, this.annotations);
return new Bindable<>(this.type, this.boxedType, value, this.annotations, this.attributes);
}
/**
@@ -171,7 +204,7 @@ public final class Bindable<T> {
* @return an updated {@link Bindable}
*/
public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) {
return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations);
return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations, this.attributes);
}
/**
@@ -244,7 +277,7 @@ public final class Bindable<T> {
public static <T> Bindable<T> of(ResolvableType type) {
Assert.notNull(type, "Type must not be null");
ResolvableType boxedType = box(type);
return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS);
return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS, NO_ATTRIBUTES);
}
private static ResolvableType box(ResolvableType type) {

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2012-2020 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.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.annotation.MergedAnnotation;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertiesBeanDefinition}.
*
* @author Phillip Webb
*/
class ConfigurationPropertiesBeanDefinitionTests {
@Test
void getAnnotationGetsAnnotation() {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
BeanDefinition definition = new ConfigurationPropertiesBeanDefinition(Example.class, annotation);
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(definition)).isSameAs(annotation);
}
@Test
void getAnnotationWhenNullReturnsMissing() {
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(null)).isEqualTo(MergedAnnotation.missing());
}
@Test
void getAnnotationWhenNoAttributeReturnsMissing() {
GenericBeanDefinition definition = new GenericBeanDefinition();
assertThat(ConfigurationPropertiesBeanDefinition.getAnnotation(definition))
.isEqualTo(MergedAnnotation.missing());
}
static class Example {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -22,6 +22,7 @@ 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.annotation.MergedAnnotation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -74,11 +75,29 @@ class ConfigurationPropertiesBeanRegistrarTests {
}
@Test
void registerWhenNotValueObjectRegistersGenericBeanDefinition() {
void registerWhenNotValueObjectRegistersConfigurationPropertiesBeanDefinition() {
String beanName = MultiConstructorBeanConfigurationProperties.class.getName();
this.registrar.register(MultiConstructorBeanConfigurationProperties.class);
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isInstanceOf(GenericBeanDefinition.class);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test
void registerWhenDeduceBindConstructorRegistersValueObjectBeanDefinition() {
String beanName = DeducedValueObjectConfigurationProperties.class.getName();
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
this.registrar.register(DeducedValueObjectConfigurationProperties.class, annotation, true);
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
}
@Test
void registerWhenDeduceBindConstructorRegistersJavaBeanObjectBeanDefinition() {
String beanName = DeducedJavaBeanConfigurationProperties.class.getName();
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotation.of(ConfigurationProperties.class);
this.registrar.register(DeducedJavaBeanConfigurationProperties.class, annotation, true);
BeanDefinition definition = this.registry.getBeanDefinition(beanName);
assertThat(definition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@ConfigurationProperties(prefix = "beancp")
@@ -110,4 +129,15 @@ class ConfigurationPropertiesBeanRegistrarTests {
}
static class DeducedValueObjectConfigurationProperties {
DeducedValueObjectConfigurationProperties(String name) {
}
}
static class DeducedJavaBeanConfigurationProperties {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -30,6 +30,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@@ -202,8 +203,8 @@ class ConfigurationPropertiesBeanTests {
@Test
void forValueObjectReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
.forValueObject(ConstructorBindingOnConstructor.class, "valueObjectBean");
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.forValueObject(
ConstructorBindingOnConstructor.class, "valueObjectBean", MergedAnnotation.missing(), false);
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(ConstructorBindingOnConstructor.class);
@@ -213,17 +214,34 @@ class ConfigurationPropertiesBeanTests {
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
assertThat(target.getValue()).isNull();
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull();
.getBindConstructor(ConstructorBindingOnConstructor.class, false, false)).isNotNull();
}
@Test
void forValueObjectWhenDeduceConstructorReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
.forValueObject(DeducedConstructorBinding.class, "valueObjectBean", MergedAnnotation.missing(), true);
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(DeducedConstructorBinding.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(DeducedConstructorBinding.class));
assertThat(target.getValue()).isNull();
assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE
.getBindConstructor(ConstructorBindingOnConstructor.class, false, false)).isNotNull();
}
@Test
void forValueObjectWhenJavaBeanBindTypeThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(AnnotatedBean.class, "annotatedBean"))
.isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(AnnotatedBean.class, "annotatedBean",
MergedAnnotation.missing(), false))
.withMessage("Bean 'annotatedBean' is not a @ConfigurationProperties value object");
assertThatIllegalStateException()
.isThrownBy(
() -> ConfigurationPropertiesBean.forValueObject(NonAnnotatedBean.class, "nonAnnotatedBean"))
.isThrownBy(() -> ConfigurationPropertiesBean.forValueObject(NonAnnotatedBean.class, "nonAnnotatedBean",
MergedAnnotation.missing(), false))
.withMessage("Bean 'nonAnnotatedBean' is not a @ConfigurationProperties value object");
}
@@ -241,7 +259,7 @@ class ConfigurationPropertiesBeanTests {
}
@Test
void bindTypeForTypeWhenNoConstructorBindingOnConstructorReturnsValueObject() {
void bindTypeForTypeWhenConstructorBindingOnConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
}
@@ -254,6 +272,18 @@ class ConfigurationPropertiesBeanTests {
+ " has more than one @ConstructorBinding constructor");
}
@Test
void bindTypeForTypeWhenDeducedConstructorBindingOnValueObjectReturnsValueObject() {
BindMethod bindType = BindMethod.forType(DeducedConstructorBinding.class, true);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
}
@Test
void bindTypeForTypeWhenDeducedConstructorBindingOnJavaBeanReturnsJavABean() {
BindMethod bindType = BindMethod.forType(NonAnnotatedBean.class, true);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
private void get(Class<?> configuration, String beanName, ThrowingConsumer<ConfigurationPropertiesBean> consumer)
throws Throwable {
get(configuration, beanName, true, consumer);
@@ -474,6 +504,14 @@ class ConfigurationPropertiesBeanTests {
}
@ConfigurationProperties
static class DeducedConstructorBinding {
DeducedConstructorBinding(String name) {
}
}
@Configuration(proxyBeanMethods = false)
@Import(NonAnnotatedBeanConfigurationImportSelector.class)
static class NonAnnotatedBeanImportConfiguration {

View File

@@ -21,7 +21,6 @@ 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.boot.context.properties.scan.combined.c.CombinedConfiguration;
import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration;
import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration;
@@ -53,8 +52,8 @@ class ConfigurationPropertiesScanRegistrarTests {
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
BeanDefinition barDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$BarProperties");
assertThat(bingDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(bingDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
assertThat(fooDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
assertThat(barDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
}
@@ -66,7 +65,7 @@ class ConfigurationPropertiesScanRegistrarTests {
getAnnotationMetadata(ConfigurationPropertiesScanConfiguration.TestConfiguration.class), beanFactory);
BeanDefinition fooDefinition = beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration$FooProperties");
assertThat(fooDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(fooDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test
@@ -85,11 +84,11 @@ class ConfigurationPropertiesScanRegistrarTests {
"b.first-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BFirstProperties");
BeanDefinition bSecondDefinition = beanFactory.getBeanDefinition(
"b.second-org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration$BSecondProperties");
assertThat(aDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(aDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
// Constructor injection
assertThat(bFirstDefinition).isExactlyInstanceOf(ConfigurationPropertiesValueObjectBeanDefinition.class);
// Post-processing injection
assertThat(bSecondDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(bSecondDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@@ -22,7 +22,6 @@ 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;
@@ -52,11 +51,11 @@ class EnableConfigurationPropertiesRegistrarTests {
}
@Test
void typeWithDefaultConstructorShouldRegisterGenericBeanDefinition() throws Exception {
void typeWithDefaultConstructorShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception {
register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("foo-" + getClass().getName() + "$FooProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test
@@ -68,11 +67,11 @@ class EnableConfigurationPropertiesRegistrarTests {
}
@Test
void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition() throws Exception {
void typeWithMultipleConstructorsShouldRegisterConfigurationPropertiesBeanDefinition() throws Exception {
register(TestConfiguration.class);
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("bing-" + getClass().getName() + "$BingProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2012-2020 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.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ImportConfigurationPropertiesBean}.
*
* @author Phillip Webb
*/
class ImportConfigurationPropertiesBeanTests {
@Test
void importJavaBean() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring");
context.register(JavaBeanConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
}
}
@Test
void importValueObject() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.value=spring");
context.register(ValueObjectConfig.class);
context.refresh();
assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("spring");
}
}
@Test
void importMultiConstructorValueObjectFails() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring");
context.register(MultiConstructorValueObjectConfig.class);
assertThatIllegalStateException().isThrownBy(context::refresh).withMessageContaining("Unable to deduce");
}
}
@Test
void importMultipleTypes() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "test.name=spring",
"test.value=boot");
context.register(ImportMultipleTypesConfig.class);
context.refresh();
assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("boot");
}
}
@Test
void importRepeatedAnnotations() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context.getEnvironment(), "jb.name=spring",
"vo.value=boot");
context.register(ImportRepeatedAnnotationsConfig.class);
context.refresh();
// assertThat(context.getBean(JavaBean.class).getName()).isEqualTo("spring");
// assertThat(context.getBean(ValueObject.class).getValue()).isEqualTo("boot");
}
}
@Configuration
@ImportConfigurationPropertiesBean(prefix = "test", type = JavaBean.class)
static class JavaBeanConfig {
}
static class JavaBean {
private String name;
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
}
@Configuration
@ImportConfigurationPropertiesBean(prefix = "test", type = ValueObject.class)
static class ValueObjectConfig {
}
static class ValueObject {
private final String value;
ValueObject(String value) {
this.value = value;
}
String getValue() {
return this.value;
}
}
@Configuration
@ImportConfigurationPropertiesBean(prefix = "test", type = MultiConstructorValueObject.class)
static class MultiConstructorValueObjectConfig {
}
static class MultiConstructorValueObject {
MultiConstructorValueObject() {
}
MultiConstructorValueObject(String name) {
}
MultiConstructorValueObject(String name, int age) {
}
}
@Configuration
@ImportConfigurationPropertiesBean(type = { ValueObject.class, JavaBean.class }, prefix = "test")
static class ImportMultipleTypesConfig {
}
@Configuration
@ImportConfigurationPropertiesBean(type = ValueObject.class, prefix = "vo")
@ImportConfigurationPropertiesBean(type = JavaBean.class, prefix = "jb")
static class ImportRepeatedAnnotationsConfig {
}
}

View File

@@ -139,6 +139,19 @@ class BindableTests {
assertThat(Bindable.of(String.class).withAnnotations(annotation).getAnnotation(Bean.class)).isNull();
}
@Test
void withAttributeShouldSetAttribute() {
Bindable<String> bindable = Bindable.of(String.class);
Bindable<String> withOne = bindable.withAttribute("one", 1);
Bindable<String> withOneAndTwo = withOne.withAttribute("two", 2);
assertThat(bindable.getAttribute("one")).isNull();
assertThat(bindable.getAttribute("two")).isNull();
assertThat(withOne.getAttribute("one")).isEqualTo(1);
assertThat(withOne.getAttribute("two")).isNull();
assertThat(withOneAndTwo.getAttribute("one")).isEqualTo(1);
assertThat(withOneAndTwo.getAttribute("two")).isEqualTo(2);
}
@Test
void toStringShouldShowDetails() {
Annotation annotation = AnnotationUtils.synthesizeAnnotation(TestAnnotation.class);
@@ -154,9 +167,10 @@ class BindableTests {
Bindable<String> bindable1 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation);
Bindable<String> bindable2 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation);
Bindable<String> bindable3 = Bindable.of(String.class).withExistingValue("fof").withAnnotations(annotation);
Bindable<String> bindable4 = Bindable.of(String.class).withExistingValue("foo").withAnnotations(annotation)
.withAttribute("bar", "bar");
assertThat(bindable1.hashCode()).isEqualTo(bindable2.hashCode());
assertThat(bindable1).isEqualTo(bindable1).isEqualTo(bindable2);
assertThat(bindable1).isEqualTo(bindable3);
assertThat(bindable1).isEqualTo(bindable1).isEqualTo(bindable2).isEqualTo(bindable3).isNotEqualTo(bindable4);
}
@Test // gh-18218

View File

@@ -3,7 +3,6 @@ package org.springframework.boot.context.properties
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
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
@@ -24,7 +23,7 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
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)
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java)
}
@Test
@@ -41,7 +40,7 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
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)
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java)
}
@ConfigurationProperties(prefix = "foo")