Merge branch '3.0.x'

Closes gh-34411
This commit is contained in:
Phillip Webb 2023-02-28 14:59:33 -08:00
commit a7f2be8b14
3 changed files with 66 additions and 5 deletions

View File

@ -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);
} }

View File

@ -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) {
}
} }

View File

@ -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