Skip to content

Commit c30f981

Browse files
committed
Fix detection of Autowired constructor with Kotlin
Previously, the import selector wrongly assumed that we should not use constructor injection with Kotlin. Rather than looking up for the primary constructor, we retrieve available constructors on the Java counter-part. This commit applies the same logic as in the constructor parameter binder and checks for the primary constructor for Kotlin types. See gh-8762
1 parent 7675802 commit c30f981

File tree

2 files changed

+111
-3
lines changed

2 files changed

+111
-3
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.springframework.boot.context.properties;
1818

1919
import java.lang.reflect.Constructor;
20+
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collections;
2223
import java.util.List;
2324
import java.util.stream.Collectors;
2425

26+
import org.springframework.beans.BeanUtils;
2527
import org.springframework.beans.factory.BeanFactory;
2628
import org.springframework.beans.factory.annotation.Autowired;
2729
import org.springframework.beans.factory.config.BeanDefinition;
@@ -30,6 +32,7 @@
3032
import org.springframework.beans.factory.support.GenericBeanDefinition;
3133
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
3234
import org.springframework.context.annotation.ImportSelector;
35+
import org.springframework.core.KotlinDetector;
3336
import org.springframework.core.annotation.AnnotationUtils;
3437
import org.springframework.core.type.AnnotationMetadata;
3538
import org.springframework.util.Assert;
@@ -52,6 +55,8 @@
5255
*/
5356
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
5457

58+
private static boolean KOTLIN_PRESENT = KotlinDetector.isKotlinPresent();
59+
5560
private static final String[] IMPORTS = {
5661
ConfigurationPropertiesBeanRegistrar.class.getName(),
5762
ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };
@@ -145,13 +150,29 @@ private BeanDefinition createBeanDefinition(
145150
}
146151

147152
private boolean canBindAtCreationTime(Class<?> type) {
148-
Constructor<?>[] constructors = type.getDeclaredConstructors();
149-
boolean autowiredPresent = Arrays.stream(constructors).anyMatch(
153+
List<Constructor<?>> constructors = determineConstructors(type);
154+
boolean autowiredPresent = constructors.stream().anyMatch(
150155
(c) -> AnnotationUtils.findAnnotation(c, Autowired.class) != null);
151156
if (autowiredPresent) {
152157
return false;
153158
}
154-
return (constructors.length == 1 && constructors[0].getParameterCount() > 0);
159+
return (constructors.size() == 1
160+
&& constructors.get(0).getParameterCount() > 0);
161+
}
162+
163+
private List<Constructor<?>> determineConstructors(Class<?> type) {
164+
List<Constructor<?>> constructors = new ArrayList<>();
165+
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
166+
Constructor<?> primaryConstructor = BeanUtils
167+
.findPrimaryConstructor(type);
168+
if (primaryConstructor != null) {
169+
constructors.add(primaryConstructor);
170+
}
171+
}
172+
else {
173+
constructors.addAll(Arrays.asList(type.getDeclaredConstructors()));
174+
}
175+
return constructors;
155176
}
156177

157178
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.springframework.boot.context.properties
2+
3+
import org.assertj.core.api.Assertions.assertThat
4+
import org.junit.Test
5+
import org.springframework.beans.factory.annotation.Autowired
6+
import org.springframework.beans.factory.support.DefaultListableBeanFactory
7+
import org.springframework.beans.factory.support.GenericBeanDefinition
8+
import org.springframework.core.type.AnnotationMetadata
9+
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory
10+
11+
/**
12+
* Tests for `EnableConfigurationPropertiesImportSelector`.
13+
*
14+
* @author Stephane Nicoll
15+
*/
16+
@Suppress("unused")
17+
class KotlinEnableConfigurationPropertiesImportSelectorTests {
18+
19+
private val registrar = EnableConfigurationPropertiesImportSelector.ConfigurationPropertiesBeanRegistrar()
20+
21+
private val beanFactory = DefaultListableBeanFactory()
22+
23+
24+
@Test
25+
fun `type with default constructor should register generic bean definition`() {
26+
this.registrar.registerBeanDefinitions(
27+
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
28+
val beanDefinition = this.beanFactory.getBeanDefinition(
29+
"foo-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$FooProperties")
30+
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
31+
}
32+
33+
@Test
34+
fun `type with autowired on constructor should register generic bean definition`() {
35+
this.registrar.registerBeanDefinitions(
36+
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
37+
val beanDefinition = this.beanFactory.getBeanDefinition(
38+
"bar-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BarProperties")
39+
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
40+
}
41+
42+
@Test
43+
fun `type with primary constructor and no autowired should register configuration properties bean definition`() {
44+
this.registrar.registerBeanDefinitions(
45+
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
46+
val beanDefinition = this.beanFactory.getBeanDefinition(
47+
"baz-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BazProperties")
48+
assertThat(beanDefinition).isExactlyInstanceOf(ConfigurationPropertiesBeanDefinition::class.java)
49+
}
50+
51+
@Test
52+
fun `type with no primary constructor should register generic bean definition`() {
53+
this.registrar.registerBeanDefinitions(
54+
getAnnotationMetadata(TestConfiguration::class.java), this.beanFactory)
55+
val beanDefinition = this.beanFactory.getBeanDefinition(
56+
"bing-org.springframework.boot.context.properties.KotlinEnableConfigurationPropertiesImportSelectorTests\$BingProperties")
57+
assertThat(beanDefinition).isExactlyInstanceOf(GenericBeanDefinition::class.java)
58+
}
59+
60+
private fun getAnnotationMetadata(source: Class<*>): AnnotationMetadata {
61+
return SimpleMetadataReaderFactory().getMetadataReader(source.name)
62+
.annotationMetadata
63+
}
64+
65+
66+
@EnableConfigurationProperties(FooProperties::class, BarProperties::class, BazProperties::class, BingProperties::class)
67+
class TestConfiguration
68+
69+
@ConfigurationProperties(prefix = "foo")
70+
class FooProperties
71+
72+
@ConfigurationProperties(prefix = "bar")
73+
class BarProperties @Autowired constructor(val foo: String)
74+
75+
@ConfigurationProperties(prefix = "baz")
76+
class BazProperties(val name: String?, val counter: Int = 42)
77+
78+
@ConfigurationProperties(prefix = "bing")
79+
class BingProperties {
80+
81+
constructor()
82+
83+
constructor(@Suppress("UNUSED_PARAMETER") foo: String)
84+
85+
}
86+
87+
}

0 commit comments

Comments
 (0)