Skip to content

Commit a58e98a

Browse files
committed
Fix binding to constructor bound lateinit properties
Closes gh-35603
1 parent 5ad0d49 commit a58e98a

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.beans.Introspector;
2020
import java.lang.annotation.Annotation;
2121
import java.lang.reflect.Field;
22+
import java.lang.reflect.InvocationTargetException;
2223
import java.lang.reflect.Method;
2324
import java.lang.reflect.Modifier;
2425
import java.util.Arrays;
@@ -382,11 +383,22 @@ Supplier<Object> getValue(Supplier<?> instance) {
382383
return this.getter.invoke(instance.get());
383384
}
384385
catch (Exception ex) {
386+
if (isUninitializedKotlinProperty(ex)) {
387+
return null;
388+
}
385389
throw new IllegalStateException("Unable to get value for property " + this.name, ex);
386390
}
387391
};
388392
}
389393

394+
private boolean isUninitializedKotlinProperty(Exception ex) {
395+
if (ex instanceof InvocationTargetException ite) {
396+
return "kotlin.UninitializedPropertyAccessException"
397+
.equals(ite.getTargetException().getClass().getName());
398+
}
399+
return false;
400+
}
401+
390402
boolean isSettable() {
391403
return this.setter != null;
392404
}

spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesTests.kt

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package org.springframework.boot.context.properties
218

19+
import org.junit.jupiter.api.AfterEach
320
import org.junit.jupiter.api.Test
421
import org.springframework.beans.factory.support.BeanDefinitionRegistry
522
import org.springframework.beans.factory.support.RootBeanDefinition
623
import org.springframework.context.annotation.AnnotationConfigApplicationContext
724
import org.springframework.context.annotation.Configuration
25+
import org.springframework.test.context.support.TestPropertySourceUtils
26+
27+
import org.assertj.core.api.Assertions.assertThat
828

929
/**
1030
* Tests for {@link ConfigurationProperties @ConfigurationProperties}-annotated beans.
@@ -15,24 +35,74 @@ class KotlinConfigurationPropertiesTests {
1535

1636
private var context = AnnotationConfigApplicationContext()
1737

38+
@AfterEach
39+
fun cleanUp() {
40+
this.context.close();
41+
}
42+
1843
@Test //gh-18652
1944
fun `type with constructor binding and existing singleton should not fail`() {
2045
val beanFactory = this.context.beanFactory
2146
(beanFactory as BeanDefinitionRegistry).registerBeanDefinition("foo",
2247
RootBeanDefinition(BingProperties::class.java))
2348
beanFactory.registerSingleton("foo", BingProperties(""))
24-
this.context.register(TestConfig::class.java)
49+
this.context.register(EnableConfigProperties::class.java)
2550
this.context.refresh();
2651
}
2752

53+
@Test
54+
fun `type with constructor bound lateinit property can be bound`() {
55+
this.context.register(EnableLateInitProperties::class.java)
56+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "lateinit.inner.value=alpha");
57+
this.context.refresh();
58+
assertThat(this.context.getBean(LateInitProperties::class.java).inner.value).isEqualTo("alpha")
59+
}
60+
61+
@Test
62+
fun `type with constructor bound lateinit property with default can be bound`() {
63+
this.context.register(EnableLateInitPropertiesWithDefault::class.java)
64+
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "lateinit-with-default.inner.bravo=two");
65+
this.context.refresh();
66+
val properties = this.context.getBean(LateInitPropertiesWithDefault::class.java)
67+
assertThat(properties.inner.alpha).isEqualTo("apple")
68+
assertThat(properties.inner.bravo).isEqualTo("two")
69+
}
70+
2871
@ConfigurationProperties(prefix = "foo")
2972
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String) {
3073

3174
}
3275

33-
@Configuration(proxyBeanMethods = false)
3476
@EnableConfigurationProperties
35-
internal open class TestConfig {
77+
class EnableConfigProperties {
78+
79+
}
80+
81+
@ConfigurationProperties("lateinit")
82+
class LateInitProperties {
83+
84+
lateinit var inner: Inner
85+
86+
}
87+
88+
data class Inner(val value: String)
89+
90+
@EnableConfigurationProperties(LateInitPropertiesWithDefault::class)
91+
class EnableLateInitPropertiesWithDefault {
92+
93+
}
94+
95+
@ConfigurationProperties("lateinit-with-default")
96+
class LateInitPropertiesWithDefault {
97+
98+
lateinit var inner: InnerWithDefault
99+
100+
}
101+
102+
data class InnerWithDefault(val alpha: String = "apple", val bravo: String = "banana")
103+
104+
@EnableConfigurationProperties(LateInitProperties::class)
105+
class EnableLateInitProperties {
36106

37107
}
38108

0 commit comments

Comments
 (0)