Perform binding at creation time if possible

Previously, environment binding always happened in a post processor once
the bean has been created. Constructor binding requires to perform the
binding at creating time so this commit performs binding at creation
time if possible.

When this happens, a special `ConfigurationPropertiesBeanDefinition` is
created with a supplier that invokes the binder. To avoid a case where
a bean is processed twice, the post-processor now ignores any bean that
has already been bound to the environment.

Closes gh-8762

Co-authored-by: Madhura Bhave <mbhave@pivotal.io>
This commit is contained in:
Stephane Nicoll 2019-03-07 13:48:42 +01:00
parent 7ca589d43c
commit 430571b37b
12 changed files with 491 additions and 37 deletions

View File

@ -20,6 +20,7 @@ import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
@ -46,6 +47,7 @@ public class JmxEndpointProperties {
*/
private final Properties staticNames = new Properties();
@Autowired
public JmxEndpointProperties(Environment environment) {
String defaultDomain = environment.getProperty("spring.jmx.default-domain");
if (StringUtils.hasText(defaultDomain)) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.
@ -25,6 +25,7 @@ import java.util.Set;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ -58,6 +59,7 @@ public class SessionProperties {
private final ServerProperties serverProperties;
@Autowired
public SessionProperties(ObjectProvider<ServerProperties> serverProperties) {
this.serverProperties = serverProperties.getIfUnique();
}

View File

@ -0,0 +1,74 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties;
import java.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}
* 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.bind(bindable).orElseCreate(type);
}
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);
}
}

View File

@ -33,10 +33,10 @@ public class ConfigurationPropertiesBindException extends BeanCreationException
private final ConfigurationProperties annotation;
ConfigurationPropertiesBindException(String beanName, Object bean,
ConfigurationPropertiesBindException(String beanName, Class<?> beanType,
ConfigurationProperties annotation, Exception cause) {
super(beanName, getMessage(bean, annotation), cause);
this.beanType = bean.getClass();
super(beanName, getMessage(beanType, annotation), cause);
this.beanType = beanType;
this.annotation = annotation;
}
@ -56,10 +56,11 @@ public class ConfigurationPropertiesBindException extends BeanCreationException
return this.annotation;
}
private static String getMessage(Object bean, ConfigurationProperties annotation) {
private static String getMessage(Class<?> beanType,
ConfigurationProperties annotation) {
StringBuilder message = new StringBuilder();
message.append("Could not bind properties to '");
message.append(ClassUtils.getShortName(bean.getClass())).append("' : ");
message.append(ClassUtils.getShortName(beanType)).append("' : ");
message.append("prefix=").append(annotation.prefix());
message.append(", ignoreInvalidFields=").append(annotation.ignoreInvalidFields());
message.append(", ignoreUnknownFields=").append(annotation.ignoreUnknownFields());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.
@ -21,8 +21,10 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
@ -34,6 +36,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.PropertySources;
@ -48,39 +51,51 @@ import org.springframework.validation.annotation.Validated;
* @author Stephane Nicoll
* @author Phillip Webb
*/
class ConfigurationPropertiesBinder {
class ConfigurationPropertiesBinder implements ApplicationContextAware {
private final ApplicationContext applicationContext;
/**
* The bean name that this binder is registered with.
*/
static final String BEAN_NAME = "org.springframework.boot.context.internalConfigurationPropertiesBinder";
private final PropertySources propertySources;
private final String validatorBeanName;
private final Validator configurationPropertiesValidator;
private ApplicationContext applicationContext;
private final boolean jsr303Present;
private PropertySources propertySources;
private Validator configurationPropertiesValidator;
private boolean jsr303Present;
private volatile Validator jsr303Validator;
private volatile Binder binder;
ConfigurationPropertiesBinder(ApplicationContext applicationContext,
String validatorBeanName) {
ConfigurationPropertiesBinder(String validatorBeanName) {
this.validatorBeanName = validatorBeanName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
this.propertySources = new PropertySourcesDeducer(applicationContext)
.getPropertySources();
this.configurationPropertiesValidator = getConfigurationPropertiesValidator(
applicationContext, validatorBeanName);
applicationContext, this.validatorBeanName);
this.jsr303Present = ConfigurationPropertiesJsr303Validator
.isJsr303Present(applicationContext);
}
public void bind(Bindable<?> target) {
public <T> BindResult<T> bind(Bindable<T> target) {
ConfigurationProperties annotation = target
.getAnnotation(ConfigurationProperties.class);
Assert.state(annotation != null,
() -> "Missing @ConfigurationProperties on " + target);
List<Validator> validators = getValidators(target);
BindHandler bindHandler = getBindHandler(annotation, validators);
getBinder().bind(annotation.prefix(), target, bindHandler);
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
private Validator getConfigurationPropertiesValidator(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.
@ -21,7 +21,9 @@ 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.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -53,8 +55,11 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
/**
* The bean name of the configuration properties validator.
* @deprecated see
* {@link ConfigurationPropertiesBindingPostProcessorRegistrar#VALIDATOR_BEAN_NAME}
*/
public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
@Deprecated
public static final String VALIDATOR_BEAN_NAME = ConfigurationPropertiesBindingPostProcessorRegistrar.VALIDATOR_BEAN_NAME;
private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
@ -75,8 +80,9 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
this.beanFactoryMetadata = this.applicationContext.getBean(
ConfigurationBeanFactoryMetadata.BEAN_NAME,
ConfigurationBeanFactoryMetadata.class);
this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
this.applicationContext, VALIDATOR_BEAN_NAME);
this.configurationPropertiesBinder = this.applicationContext.getBean(
ConfigurationPropertiesBinder.BEAN_NAME,
ConfigurationPropertiesBinder.class);
}
@Override
@ -89,12 +95,18 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
throws BeansException {
ConfigurationProperties annotation = getAnnotation(bean, beanName,
ConfigurationProperties.class);
if (annotation != null) {
if (annotation != null && !hasBeenBound(beanName)) {
bind(bean, beanName, annotation);
}
return bean;
}
private boolean hasBeenBound(String beanName) {
BeanDefinition beanDefinition = ((BeanDefinitionRegistry) this.applicationContext
.getAutowireCapableBeanFactory()).getBeanDefinition(beanName);
return beanDefinition instanceof ConfigurationPropertiesBeanDefinition;
}
private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
ResolvableType type = getBeanType(bean, beanName);
Validated validated = getAnnotation(bean, beanName, Validated.class);
@ -107,8 +119,8 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
this.configurationPropertiesBinder.bind(target);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
ex);
throw new ConfigurationPropertiesBindException(beanName, bean.getClass(),
annotation, ex);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.
@ -32,9 +32,17 @@ import org.springframework.core.type.AnnotationMetadata;
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);
}
if (!registry.containsBeanDefinition(
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
registerConfigurationPropertiesBindingPostProcessor(registry);
@ -42,6 +50,16 @@ public class ConfigurationPropertiesBindingPostProcessorRegistrar
}
}
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();
@ -49,7 +67,6 @@ public class ConfigurationPropertiesBindingPostProcessorRegistrar
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(
ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
}
private void registerConfigurationBeanFactoryMetadata(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.
@ -16,12 +16,15 @@
package org.springframework.boot.context.properties;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
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;
@ -89,7 +92,7 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
ConfigurableListableBeanFactory beanFactory, Class<?> type) {
String name = getName(type);
if (!containsBeanDefinition(beanFactory, name)) {
registerBeanDefinition(registry, name, type);
registerBeanDefinition(registry, beanFactory, name, type);
}
}
@ -114,12 +117,11 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
return false;
}
private void registerBeanDefinition(BeanDefinitionRegistry registry, String name,
Class<?> type) {
private void registerBeanDefinition(BeanDefinitionRegistry registry,
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
assertHasAnnotation(type);
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
registry.registerBeanDefinition(name, definition);
registry.registerBeanDefinition(name,
createBeanDefinition(beanFactory, name, type));
}
private void assertHasAnnotation(Class<?> type) {
@ -129,6 +131,29 @@ class EnableConfigurationPropertiesImportSelector implements ImportSelector {
+ " annotation found on '" + type.getName() + "'.");
}
private BeanDefinition createBeanDefinition(
ConfigurableListableBeanFactory beanFactory, String name, Class<?> type) {
if (canBindAtCreationTime(type)) {
return ConfigurationPropertiesBeanDefinition.from(beanFactory, name,
type);
}
else {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
}
}
private boolean canBindAtCreationTime(Class<?> type) {
Constructor<?>[] constructors = type.getDeclaredConstructors();
boolean autowiredPresent = Arrays.stream(constructors).anyMatch(
(c) -> AnnotationUtils.findAnnotation(c, Autowired.class) != null);
if (autowiredPresent) {
return false;
}
return (constructors.length == 1 && constructors[0].getParameterCount() > 0);
}
}
}

View File

@ -254,7 +254,8 @@ public class Binder {
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
BindHandler handler, Context context, boolean allowRecursiveBinding) {
ConfigurationProperty property = findProperty(name, context);
if (property == null && containsNoDescendantOf(context.getSources(), name)) {
if (property == null && containsNoDescendantOf(context.getSources(), name)
&& context.depth != 0) {
return null;
}
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
@ -330,8 +331,7 @@ public class Binder {
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
BindHandler handler, Context context, boolean allowRecursiveBinding) {
if (containsNoDescendantOf(context.getSources(), name)
|| isUnbindableBean(name, target, context)) {
if (isUnbindableBean(name, target, context)) {
return null;
}
Class<?> type = target.getType().resolve(Object.class);

View File

@ -41,6 +41,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -805,6 +806,46 @@ public class ConfigurationPropertiesTests {
assertThat(x.get(1).getB()).isEqualTo(1);
}
@Test
public void loadWhenConfigurationPropertiesInjectsAnotherBeanShouldNotFail() {
load(OtherInjectPropertiesConfiguration.class);
}
@Test
public void loadWhenBindingToConstructorParametersShouldBind() {
MutablePropertySources sources = this.context.getEnvironment()
.getPropertySources();
Map<String, Object> source = new HashMap<>();
source.put("test.foo", "baz");
source.put("test.bar", "5");
sources.addLast(new MapPropertySource("test", source));
load(ConstructorParameterConfiguration.class);
ConstructorParameterProperties bean = this.context
.getBean(ConstructorParameterProperties.class);
assertThat(bean.getFoo()).isEqualTo("baz");
assertThat(bean.getBar()).isEqualTo(5);
}
@Test
public void loadWhenBindingToConstructorParametersWithDefaultValuesShouldBind() {
load(ConstructorParameterConfiguration.class);
ConstructorParameterProperties bean = this.context
.getBean(ConstructorParameterProperties.class);
assertThat(bean.getFoo()).isEqualTo("hello");
assertThat(bean.getBar()).isEqualTo(0);
}
@Test
public void loadWhenBindingToConstructorParametersShouldValidate() {
assertThatExceptionOfType(Exception.class)
.isThrownBy(() -> load(ConstructorParameterValidationConfiguration.class))
.satisfies((ex) -> {
assertThat(ex).hasCauseInstanceOf(BindException.class);
assertThat(ex.getCause())
.hasCauseExactlyInstanceOf(BindValidationException.class);
});
}
private AnnotationConfigApplicationContext load(Class<?> configuration,
String... inlinedProperties) {
return load(new Class<?>[] { configuration }, inlinedProperties);
@ -1768,6 +1809,76 @@ public class ConfigurationPropertiesTests {
}
@ConfigurationProperties(prefix = "test")
static class OtherInjectedProperties {
private final DataSizeProperties dataSizeProperties;
@Autowired
OtherInjectedProperties(ObjectProvider<DataSizeProperties> dataSizeProperties) {
this.dataSizeProperties = dataSizeProperties.getIfUnique();
}
}
@Configuration
@EnableConfigurationProperties(OtherInjectedProperties.class)
static class OtherInjectPropertiesConfiguration {
}
@ConfigurationProperties(prefix = "test")
@Validated
static class ConstructorParameterProperties {
@NotEmpty
private final String foo;
private final int bar;
ConstructorParameterProperties(
@ConfigurationPropertyDefaultValue("hello") String foo, int bar) {
this.foo = foo;
this.bar = bar;
}
public String getFoo() {
return this.foo;
}
public int getBar() {
return this.bar;
}
}
@ConfigurationProperties(prefix = "test")
@Validated
static class ConstructorParameterValidatedProperties {
@NotEmpty
private final String foo;
ConstructorParameterValidatedProperties(String foo) {
this.foo = foo;
}
public String getFoo() {
return this.foo;
}
}
@EnableConfigurationProperties(ConstructorParameterProperties.class)
static class ConstructorParameterConfiguration {
}
@EnableConfigurationProperties(ConstructorParameterValidatedProperties.class)
static class ConstructorParameterValidationConfiguration {
}
static class CustomPropertiesValidator implements Validator {
@Override

View File

@ -0,0 +1,195 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties;
import java.io.IOException;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
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 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.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link EnableConfigurationPropertiesImportSelector}.
*
* @author Madhura Bhave
* @author Stephane Nicoll
*/
public class EnableConfigurationPropertiesImportSelectorTests {
private final EnableConfigurationPropertiesImportSelector importSelector = new EnableConfigurationPropertiesImportSelector();
private final EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar registrar = new EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar();
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@Test
public void selectImports() {
String[] imports = this.importSelector
.selectImports(mock(AnnotationMetadata.class));
assertThat(imports).containsExactly(
EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar.class
.getName(),
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName());
}
@Test
public void typeWithDefaultConstructorShouldRegisterGenericBeanDefinition()
throws Exception {
this.registrar.registerBeanDefinitions(
getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
"foo-org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelectorTests$FooProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test
public void typeWithAutowiredOnConstructorShouldRegisterGenericBeanDefinition()
throws Exception {
this.registrar.registerBeanDefinitions(
getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelectorTests$BarProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test
public void typeWithOneConstructorWithParametersShouldRegisterConfigurationPropertiesBeanDefinition()
throws Exception {
this.registrar.registerBeanDefinitions(
getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
"baz-org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelectorTests$BazProperties");
assertThat(beanDefinition)
.isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition.class);
}
@Test
public void typeWithMultipleConstructorsShouldRegisterGenericBeanDefinition()
throws Exception {
this.registrar.registerBeanDefinitions(
getAnnotationMetadata(TestConfiguration.class), this.beanFactory);
BeanDefinition beanDefinition = this.beanFactory.getBeanDefinition(
"bing-org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelectorTests$BingProperties");
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition.class);
}
@Test
public void typeWithNoAnnotationShouldFail() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.registrar.registerBeanDefinitions(
getAnnotationMetadata(InvalidConfiguration.class),
this.beanFactory))
.withMessageContaining("No ConfigurationProperties annotation found")
.withMessageContaining(
EnableConfigurationPropertiesImportSelectorTests.class.getName());
}
@Test
public void registrationWithDuplicatedTypeShouldRegisterSingleBeanDefinition()
throws IOException {
DefaultListableBeanFactory factory = spy(this.beanFactory);
this.registrar.registerBeanDefinitions(
getAnnotationMetadata(DuplicateConfiguration.class), factory);
verify(factory, times(1)).registerBeanDefinition(anyString(), any());
}
@Test
public 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,
BazProperties.class, BingProperties.class })
static class TestConfiguration {
}
@EnableConfigurationProperties(EnableConfigurationPropertiesImportSelectorTests.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")
public static class BarProperties {
@Autowired
public BarProperties(String foo) {
}
}
@ConfigurationProperties(prefix = "baz")
public static class BazProperties {
public BazProperties(String foo) {
}
}
@ConfigurationProperties(prefix = "bing")
public static class BingProperties {
public BingProperties() {
}
public BingProperties(String foo) {
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2018 the original author or authors.
* 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.