Support binding with multiple constructors

Update `DefaultBindConstructorProvider` so that binding to objects with
multiple constructors is allowed, as long as there is only one
non-public candidate.

Closes gh-23117
pull/23127/head
Phillip Webb 4 years ago
parent cf9e85ca53
commit c613d119f7

@ -17,6 +17,7 @@
package org.springframework.boot.context.properties.bind; package org.springframework.boot.context.properties.bind;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
@ -42,6 +43,18 @@ class DefaultBindConstructorProvider implements BindConstructorProvider {
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) { if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
return constructors[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; return null;
} }

@ -123,6 +123,18 @@ class ValueObjectBinderTests {
assertThat(bound).isFalse(); 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 @Test
void bindToClassShouldBindNested() { void bindToClassShouldBindNested() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource(); MockConfigurationPropertySource source = new MockConfigurationPropertySource();
@ -341,7 +353,6 @@ class ValueObjectBinderTests {
Bindable<NamedParameter> target = Bindable.of(NamedParameter.class); Bindable<NamedParameter> target = Bindable.of(NamedParameter.class);
NamedParameter bound = this.binder.bindOrCreate("test", target); NamedParameter bound = this.binder.bindOrCreate("test", target);
assertThat(bound.getImportName()).isEqualTo("test"); assertThat(bound.getImportName()).isEqualTo("test");
} }
private void noConfigurationProperty(BindException ex) { 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 { abstract static class ExampleAbstractBean {
private final String name; private final String name;

Loading…
Cancel
Save