commit
a7f2be8b14
|
|
@ -21,6 +21,8 @@ import java.lang.reflect.Modifier;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import kotlin.jvm.JvmClassMappingKt;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.KotlinDetector;
|
import org.springframework.core.KotlinDetector;
|
||||||
|
|
@ -41,7 +43,8 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||||
Constructors constructors = Constructors.getConstructors(bindable.getType().resolve(),
|
Constructors constructors = Constructors.getConstructors(bindable.getType().resolve(),
|
||||||
isNestedConstructorBinding);
|
isNestedConstructorBinding);
|
||||||
if (constructors.getBind() != null && constructors.isDeducedBindConstructor()) {
|
if (constructors.getBind() != null && constructors.isDeducedBindConstructor()
|
||||||
|
&& !constructors.isImmutableType()) {
|
||||||
if (bindable.getValue() != null && bindable.getValue().get() != null) {
|
if (bindable.getValue() != null && bindable.getValue().get() != null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +63,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
*/
|
*/
|
||||||
static final class Constructors {
|
static final class Constructors {
|
||||||
|
|
||||||
private static final Constructors NONE = new Constructors(false, null, false);
|
private static final Constructors NONE = new Constructors(false, null, false, false);
|
||||||
|
|
||||||
private final boolean hasAutowired;
|
private final boolean hasAutowired;
|
||||||
|
|
||||||
|
|
@ -68,10 +71,14 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
|
|
||||||
private final boolean deducedBindConstructor;
|
private final boolean deducedBindConstructor;
|
||||||
|
|
||||||
private Constructors(boolean hasAutowired, Constructor<?> bind, boolean deducedBindConstructor) {
|
private final boolean immutableType;
|
||||||
|
|
||||||
|
private Constructors(boolean hasAutowired, Constructor<?> bind, boolean deducedBindConstructor,
|
||||||
|
boolean immutableType) {
|
||||||
this.hasAutowired = hasAutowired;
|
this.hasAutowired = hasAutowired;
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
this.deducedBindConstructor = deducedBindConstructor;
|
this.deducedBindConstructor = deducedBindConstructor;
|
||||||
|
this.immutableType = immutableType;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasAutowired() {
|
boolean hasAutowired() {
|
||||||
|
|
@ -86,6 +93,10 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
return this.deducedBindConstructor;
|
return this.deducedBindConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isImmutableType() {
|
||||||
|
return this.immutableType;
|
||||||
|
}
|
||||||
|
|
||||||
static Constructors getConstructors(Class<?> type, boolean isNestedConstructorBinding) {
|
static Constructors getConstructors(Class<?> type, boolean isNestedConstructorBinding) {
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
return NONE;
|
return NONE;
|
||||||
|
|
@ -93,13 +104,15 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
boolean hasAutowiredConstructor = isAutowiredPresent(type);
|
boolean hasAutowiredConstructor = isAutowiredPresent(type);
|
||||||
Constructor<?>[] candidates = getCandidateConstructors(type);
|
Constructor<?>[] candidates = getCandidateConstructors(type);
|
||||||
MergedAnnotations[] candidateAnnotations = getAnnotations(candidates);
|
MergedAnnotations[] candidateAnnotations = getAnnotations(candidates);
|
||||||
|
boolean kotlinType = isKotlinType(type);
|
||||||
boolean deducedBindConstructor = false;
|
boolean deducedBindConstructor = false;
|
||||||
|
boolean immutableType = type.isRecord() || isKotlinDataClass(type);
|
||||||
Constructor<?> bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations);
|
Constructor<?> bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations);
|
||||||
if (bind == null && !hasAutowiredConstructor) {
|
if (bind == null && !hasAutowiredConstructor) {
|
||||||
bind = deduceBindConstructor(type, candidates);
|
bind = deduceBindConstructor(type, candidates);
|
||||||
deducedBindConstructor = bind != null;
|
deducedBindConstructor = bind != null;
|
||||||
}
|
}
|
||||||
if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) {
|
if (bind == null && !hasAutowiredConstructor && kotlinType) {
|
||||||
bind = deduceKotlinBindConstructor(type);
|
bind = deduceKotlinBindConstructor(type);
|
||||||
deducedBindConstructor = bind != null;
|
deducedBindConstructor = bind != null;
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +120,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
Assert.state(!hasAutowiredConstructor,
|
Assert.state(!hasAutowiredConstructor,
|
||||||
() -> type.getName() + " declares @ConstructorBinding and @Autowired constructor");
|
() -> type.getName() + " declares @ConstructorBinding and @Autowired constructor");
|
||||||
}
|
}
|
||||||
return new Constructors(hasAutowiredConstructor, bind, deducedBindConstructor);
|
return new Constructors(hasAutowiredConstructor, bind, deducedBindConstructor, immutableType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAutowiredPresent(Class<?> type) {
|
private static boolean isAutowiredPresent(Class<?> type) {
|
||||||
|
|
@ -185,6 +198,10 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
|
||||||
return (result != null && result.getParameterCount() > 0) ? result : null;
|
return (result != null && result.getParameterCount() > 0) ? result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isKotlinDataClass(Class<?> type) {
|
||||||
|
return isKotlinType(type) && JvmClassMappingKt.getKotlinClass(type).isData();
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isKotlinType(Class<?> type) {
|
private static boolean isKotlinType(Class<?> type) {
|
||||||
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type);
|
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1135,6 +1135,13 @@ class ConfigurationPropertiesTests {
|
||||||
assertThat(bean.getNested().getTwo()).isEqualTo("bound-2");
|
assertThat(bean.getNested().getTwo()).isEqualTo("bound-2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // gh-34407
|
||||||
|
void loadWhenNestedRecordWithExistingInstance() {
|
||||||
|
load(NestedRecordInstancePropertiesConfiguration.class, "test.nested.name=spring");
|
||||||
|
NestedRecordInstanceProperties bean = this.context.getBean(NestedRecordInstanceProperties.class);
|
||||||
|
assertThat(bean.getNested().name()).isEqualTo("spring");
|
||||||
|
}
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
|
private AnnotationConfigApplicationContext load(Class<?> configuration, String... inlinedProperties) {
|
||||||
return load(new Class<?>[] { configuration }, inlinedProperties);
|
return load(new Class<?>[] { configuration }, inlinedProperties);
|
||||||
}
|
}
|
||||||
|
|
@ -2932,4 +2939,29 @@ class ConfigurationPropertiesTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableConfigurationProperties(NestedRecordInstanceProperties.class)
|
||||||
|
static class NestedRecordInstancePropertiesConfiguration {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
static class NestedRecordInstanceProperties {
|
||||||
|
|
||||||
|
@NestedConfigurationProperty
|
||||||
|
private NestedRecord nested = new NestedRecord("unnamed");
|
||||||
|
|
||||||
|
NestedRecord getNested() {
|
||||||
|
return this.nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNested(NestedRecord nestedRecord) {
|
||||||
|
this.nested = nestedRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static record NestedRecord(String name) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,14 @@ class DefaultBindConstructorProviderTests {
|
||||||
assertThat(bindConstructor).isNotNull();
|
assertThat(bindConstructor).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getBindConstructorWhenHasExistingValueAndValueIsRecordReturnsConstructor() {
|
||||||
|
OneConstructorOnRecord existingValue = new OneConstructorOnRecord("name", 123);
|
||||||
|
Bindable<?> bindable = Bindable.of(OneConstructorOnRecord.class).withExistingValue(existingValue);
|
||||||
|
Constructor<?> bindConstructor = this.provider.getBindConstructor(bindable, false);
|
||||||
|
assertThat(bindConstructor).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
static class OnlyDefaultConstructor {
|
static class OnlyDefaultConstructor {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +207,10 @@ class DefaultBindConstructorProviderTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static record OneConstructorOnRecord(String name, int age) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static class TwoConstructorsWithBothConstructorBinding {
|
static class TwoConstructorsWithBothConstructorBinding {
|
||||||
|
|
||||||
@ConstructorBinding
|
@ConstructorBinding
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue