diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector.java index 09d4e2994999..0cbe162d2b66 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector.java @@ -17,11 +17,13 @@ package org.springframework.boot.context.properties; import java.lang.reflect.Constructor; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; @@ -30,6 +32,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.KotlinDetector; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; @@ -52,6 +55,8 @@ */ class EnableConfigurationPropertiesImportSelector implements ImportSelector { + private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent(); + private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(), ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }; @@ -145,13 +150,29 @@ private BeanDefinition createBeanDefinition( } private boolean canBindAtCreationTime(Class type) { - Constructor[] constructors = type.getDeclaredConstructors(); - boolean autowiredPresent = Arrays.stream(constructors).anyMatch( + List> constructors = determineConstructors(type); + boolean autowiredPresent = constructors.stream().anyMatch( (c) -> AnnotationUtils.findAnnotation(c, Autowired.class) != null); if (autowiredPresent) { return false; } - return (constructors.length == 1 && constructors[0].getParameterCount() > 0); + return (constructors.size() == 1 + && constructors.get(0).getParameterCount() > 0); + } + + private List> determineConstructors(Class type) { + List> 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; } } diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinEnableConfigurationPropertiesImportSelectorTests.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinEnableConfigurationPropertiesImportSelectorTests.kt new file mode 100644 index 000000000000..e0f091beb2bd --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinEnableConfigurationPropertiesImportSelectorTests.kt @@ -0,0 +1,87 @@ +package org.springframework.boot.context.properties + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.springframework.beans.factory.annotation.Autowired +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 + +/** + * Tests for `EnableConfigurationPropertiesImportSelector`. + * + * @author Stephane Nicoll + */ +@Suppress("unused") +class KotlinEnableConfigurationPropertiesImportSelectorTests { + + private val registrar = EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar() + + private val beanFactory = DefaultListableBeanFactory() + + + @Test + fun `type with default constructor should register generic bean definition`() { + this.registrar.registerBeanDefinitions( + getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory) + val beanDefinition = this.beanFactory.getBeanDefinition( + "foo-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$FooProperties") + assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java) + } + + @Test + fun `type with autowired on constructor should register generic bean definition`() { + this.registrar.registerBeanDefinitions( + getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory) + val beanDefinition = this.beanFactory.getBeanDefinition( + "bar-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BarProperties") + assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java) + } + + @Test + fun `type with primary constructor and no autowired should register configuration properties bean definition`() { + this.registrar.registerBeanDefinitions( + getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory) + val beanDefinition = this.beanFactory.getBeanDefinition( + "baz-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BazProperties") + assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java) + } + + @Test + fun `type with no primary constructor should register generic bean definition`() { + this.registrar.registerBeanDefinitions( + getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory) + val beanDefinition = this.beanFactory.getBeanDefinition( + "bing-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BingProperties") + assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java) + } + + private fun getAnnotationMetadata(source: Class<*>): AnnotationMetadata { + return SimpleMetadataReaderFactory().getMetadataReader(source.name) + .annotationMetadata + } + + + @EnableConfigurationProperties(FooProperties::class, BarProperties::class, BazProperties::class, BingProperties::class) + class TestConfiguration + + @ConfigurationProperties(prefix = "foo") + class FooProperties + + @ConfigurationProperties(prefix = "bar") + class BarProperties @Autowired constructor(val foo: String) + + @ConfigurationProperties(prefix = "baz") + class BazProperties(val name: String?, val counter: Int = 42) + + @ConfigurationProperties(prefix = "bing") + class BingProperties { + + constructor() + + constructor(@Suppress("UNUSED_PARAMETER") foo: String) + + } + +} \ No newline at end of file