diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java index b36b12b2cc..260e83c0e6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java @@ -17,6 +17,7 @@ package org.springframework.boot.context.properties.bind; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; import org.springframework.beans.BeanUtils; import org.springframework.core.KotlinDetector; @@ -42,6 +43,18 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { if (constructors.length == 1 && constructors[0].getParameterCount() > 0) { return constructors[0]; } + Constructor constructor = null; + for (Constructor candidate : constructors) { + if (!Modifier.isPrivate(candidate.getModifiers())) { + if (constructor != null) { + return null; + } + constructor = candidate; + } + } + if (constructor != null && constructor.getParameterCount() > 0) { + return constructor; + } return null; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index fa78b82b28..dee7a976a9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -123,6 +123,18 @@ class ValueObjectBinderTests { assertThat(bound).isFalse(); } + @Test + void bindToClassWithMultipleConstructorsWhenOnlyOneIsNotPrivateShouldBind() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.int-value", "12"); + this.sources.add(source); + MultipleConstructorsOnlyOneNotPrivateBean bean = this.binder + .bind("foo", Bindable.of(MultipleConstructorsOnlyOneNotPrivateBean.class)).get(); + bean = bean.withString("test"); + assertThat(bean.getIntValue()).isEqualTo(12); + assertThat(bean.getStringValue()).isEqualTo("test"); + } + @Test void bindToClassShouldBindNested() { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); @@ -341,7 +353,6 @@ class ValueObjectBinderTests { Bindable target = Bindable.of(NamedParameter.class); NamedParameter bound = this.binder.bindOrCreate("test", target); assertThat(bound.getImportName()).isEqualTo("test"); - } private void noConfigurationProperty(BindException ex) { @@ -417,6 +428,35 @@ class ValueObjectBinderTests { } + static class MultipleConstructorsOnlyOneNotPrivateBean { + + private final int intValue; + + private final String stringValue; + + MultipleConstructorsOnlyOneNotPrivateBean(int intValue) { + this(intValue, 23L, "hello"); + } + + private MultipleConstructorsOnlyOneNotPrivateBean(int intValue, long longValue, String stringValue) { + this.intValue = intValue; + this.stringValue = stringValue; + } + + int getIntValue() { + return this.intValue; + } + + String getStringValue() { + return this.stringValue; + } + + MultipleConstructorsOnlyOneNotPrivateBean withString(String stringValue) { + return new MultipleConstructorsOnlyOneNotPrivateBean(this.intValue, 0, stringValue); + } + + } + abstract static class ExampleAbstractBean { private final String name;