From 6b8575b001450c63fd34a55ae6314141ec7172d9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 21 Sep 2022 21:34:34 +0100 Subject: [PATCH] Fix constructor binding to Kotlin data class with default values Closes gh-32416 --- ...rationPropertiesBindConstructorProvider.java | 17 +++++++++++++++++ ...ionPropertiesBindConstructorProviderTests.kt | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java index edc0cae5467..4d1dab6673c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java @@ -19,9 +19,11 @@ package org.springframework.boot.context.properties; import java.lang.reflect.Constructor; import java.util.Arrays; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.core.KotlinDetector; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.Assert; @@ -94,6 +96,9 @@ public class ConfigurationPropertiesBindConstructorProvider implements BindConst } bind = findAnnotatedConstructor(type, bind, candidate); } + if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) { + bind = deduceKotlinBindConstructor(type); + } return new Constructors(hasAutowiredConstructor, bind); } @@ -141,6 +146,18 @@ public class ConfigurationPropertiesBindConstructorProvider implements BindConst return constructor; } + private static boolean isKotlinType(Class type) { + return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); + } + + private static Constructor deduceKotlinBindConstructor(Class type) { + Constructor primaryConstructor = BeanUtils.findPrimaryConstructor(type); + if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { + return primaryConstructor; + } + return null; + } + } } diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt index b4df1706230..ac5efd8dc51 100644 --- a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -113,6 +113,12 @@ class ConfigurationPropertiesBindConstructorProviderTests { } } + @Test + fun `data class with default values should use constructor binding`() { + val bindConstructor = this.constructorProvider.getBindConstructor(ConstructorBindingDataClassWithDefaultValues::class.java, false) + assertThat(bindConstructor).isNotNull(); + } + @ConfigurationProperties(prefix = "foo") class FooProperties @@ -210,4 +216,7 @@ class ConfigurationPropertiesBindConstructorProviderTests { } + @ConfigurationProperties(prefix = "bing") + data class ConstructorBindingDataClassWithDefaultValues(val name: String = "Joan", val counter: Int = 42) + } \ No newline at end of file