Merge branch '3.0.x'

Closes gh-34411
pull/34383/merge
Phillip Webb 2 years ago
commit a7f2be8b14

@ -21,6 +21,8 @@ import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.stream.Stream;
import kotlin.jvm.JvmClassMappingKt;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.KotlinDetector;
@ -41,7 +43,8 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
Constructors constructors = Constructors.getConstructors(bindable.getType().resolve(),
isNestedConstructorBinding);
if (constructors.getBind() != null && constructors.isDeducedBindConstructor()) {
if (constructors.getBind() != null && constructors.isDeducedBindConstructor()
&& !constructors.isImmutableType()) {
if (bindable.getValue() != null && bindable.getValue().get() != null) {
return null;
}
@ -60,7 +63,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
*/
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;
@ -68,10 +71,14 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
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.bind = bind;
this.deducedBindConstructor = deducedBindConstructor;
this.immutableType = immutableType;
}
boolean hasAutowired() {
@ -86,6 +93,10 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
return this.deducedBindConstructor;
}
boolean isImmutableType() {
return this.immutableType;
}
static Constructors getConstructors(Class<?> type, boolean isNestedConstructorBinding) {
if (type == null) {
return NONE;
@ -93,13 +104,15 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
boolean hasAutowiredConstructor = isAutowiredPresent(type);
Constructor<?>[] candidates = getCandidateConstructors(type);
MergedAnnotations[] candidateAnnotations = getAnnotations(candidates);
boolean kotlinType = isKotlinType(type);
boolean deducedBindConstructor = false;
boolean immutableType = type.isRecord() || isKotlinDataClass(type);
Constructor<?> bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations);
if (bind == null && !hasAutowiredConstructor) {
bind = deduceBindConstructor(type, candidates);
deducedBindConstructor = bind != null;
}
if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) {
if (bind == null && !hasAutowiredConstructor && kotlinType) {
bind = deduceKotlinBindConstructor(type);
deducedBindConstructor = bind != null;
}
@ -107,7 +120,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
Assert.state(!hasAutowiredConstructor,
() -> 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) {
@ -185,6 +198,10 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
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) {
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type);
}

@ -1135,6 +1135,13 @@ class ConfigurationPropertiesTests {
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) {
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();
}
@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 {
}
@ -199,6 +207,10 @@ class DefaultBindConstructorProviderTests {
}
static record OneConstructorOnRecord(String name, int age) {
}
static class TwoConstructorsWithBothConstructorBinding {
@ConstructorBinding

Loading…
Cancel
Save