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…
Reference in New Issue