diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinitionValidator.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinitionValidator.java new file mode 100644 index 0000000000..3de20444f2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanDefinitionValidator.java @@ -0,0 +1,81 @@ +/* + * 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.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +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.Ordered; +import org.springframework.util.Assert; + +/** + * {@link BeanFactoryPostProcessor} to validate that regular bean definitions aren't + * creating {@link ConstructorBinding} beans. + * + * @author Phillip Webb + */ +class ConfigurationPropertiesBeanDefinitionValidator implements BeanFactoryPostProcessor, Ordered { + + private static final String BEAN_NAME = ConfigurationPropertiesBeanDefinitionValidator.class.getName(); + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + for (String beanName : beanFactory.getBeanDefinitionNames()) { + BeanDefinition definition = beanFactory.getBeanDefinition(beanName); + if (!(definition instanceof ConfigurationPropertiesValueObjectBeanDefinition)) { + validate(beanFactory, beanName); + } + } + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + private void validate(ConfigurableListableBeanFactory beanFactory, String beanName) { + Class beanClass = beanFactory.getType(beanName, false); + System.out.println(beanName); + if (beanClass != null && BindMethod.forClass(beanClass) == BindMethod.VALUE_OBJECT) { + throw new BeanCreationException(beanName, + "@EnableConfigurationProperties or @ConfigurationPropertiesScan must be used to add " + + "@ConstructorBinding type " + beanClass.getName()); + } + } + + /** + * Register a {@link ConfigurationPropertiesBeanDefinitionValidator} bean if one is + * not already registered. + * @param registry the bean definition registry + */ + static void register(BeanDefinitionRegistry registry) { + Assert.notNull(registry, "Registry must not be null"); + if (!registry.containsBeanDefinition(BEAN_NAME)) { + GenericBeanDefinition definition = new GenericBeanDefinition(); + definition.setBeanClass(ConfigurationPropertiesBeanDefinitionValidator.class); + definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(BEAN_NAME, definition); + } + ConfigurationPropertiesBinder.register(registry); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java index ff0c1fe78b..f5f14cea14 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.java @@ -49,6 +49,7 @@ class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegi @SuppressWarnings("deprecation") static void registerInfrastructureBeans(BeanDefinitionRegistry registry) { ConfigurationPropertiesBindingPostProcessor.register(registry); + ConfigurationPropertiesBeanDefinitionValidator.register(registry); ConfigurationBeanFactoryMetadata.register(registry); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 4ba63cdf17..5ab44292a6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -55,6 +55,7 @@ import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.Scope; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @@ -758,6 +759,13 @@ class ConfigurationPropertiesTests { assertThat(bean.getBar()).isEqualTo(5); } + @Test // gh-17831 + void loadWhenBindingConstructorParametersViaImportShouldThrowException() { + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(() -> load(ImportConstructorParameterPropertiesConfiguration.class)) + .withMessageContaining("@EnableConfigurationProperties or @ConfigurationPropertiesScan must be used"); + } + @Test void loadWhenBindingToConstructorParametersWithDefaultValuesShouldBind() { load(ConstructorParameterConfiguration.class); @@ -1797,6 +1805,13 @@ class ConfigurationPropertiesTests { } + @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties + @Import(ConstructorParameterProperties.class) + static class ImportConstructorParameterPropertiesConfiguration { + + } + @ImmutableConfigurationProperties(prefix = "test") @Validated static class ConstructorParameterValidatedProperties {