diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java index 90aeab57f7..3eb97bfd1d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java @@ -31,8 +31,15 @@ abstract class AggregateBinder { private final BindContext context; - AggregateBinder(BindContext context) { + private final boolean allowRecursiveBinding; + + AggregateBinder(BindContext context, boolean allowRecursiveBinding) { this.context = context; + this.allowRecursiveBinding = allowRecursiveBinding; + } + + boolean isAllowRecursiveBinding() { + return this.allowRecursiveBinding; } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index cf35bff252..26b7c4dad6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -188,13 +188,14 @@ public class Binder { } protected final T bind(ConfigurationPropertyName name, Bindable target, - BindHandler handler, Context context, boolean skipIfHasBoundBean) { + BindHandler handler, Context context, boolean allowRecursiveBinding) { context.clearConfigurationProperty(); try { if (!handler.onStart(name, target, context)) { return null; } - Object bound = bindObject(name, target, handler, context, skipIfHasBoundBean); + Object bound = bindObject(name, target, handler, context, + allowRecursiveBinding); return handleBindResult(name, target, handler, context, bound); } catch (Exception ex) { @@ -232,7 +233,7 @@ public class Binder { } private Object bindObject(ConfigurationPropertyName name, Bindable target, - BindHandler handler, Context context, boolean skipIfHasBoundBean) + BindHandler handler, Context context, boolean allowRecursiveBinding) throws Exception { ConfigurationProperty property = findProperty(name, context); if (property == null && containsNoDescendantOf(context.streamSources(), name)) { @@ -245,7 +246,7 @@ public class Binder { if (property != null) { return bindProperty(name, target, handler, context, property); } - return bindBean(name, target, handler, context, skipIfHasBoundBean); + return bindBean(name, target, handler, context, allowRecursiveBinding); } private AggregateBinder getAggregateBinder(Bindable target, Context context) { @@ -266,7 +267,7 @@ public class Binder { BindHandler handler, Context context, AggregateBinder aggregateBinder) { AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> { Supplier supplier = () -> bind(itemName, itemTarget, handler, context, - false); + aggregateBinder.isAllowRecursiveBinding()); return context.withSource(source, supplier); }; return context.withIncreasedDepth( @@ -290,15 +291,15 @@ public class Binder { } private Object bindBean(ConfigurationPropertyName name, Bindable target, - BindHandler handler, Context context, boolean skipIfHasBoundBean) { + BindHandler handler, Context context, boolean allowRecursiveBinding) { if (containsNoDescendantOf(context.streamSources(), name) || isUnbindableBean(name, target, context)) { return null; } BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind( - name.append(propertyName), propertyTarget, handler, context, true); + name.append(propertyName), propertyTarget, handler, context, false); Class type = target.getType().resolve(); - if (skipIfHasBoundBean && context.hasBoundBean(type)) { + if (!allowRecursiveBinding && context.hasBoundBean(type)) { return null; } return context.withBean(type, () -> { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java index d9e09fe87e..123190f71e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java @@ -45,7 +45,7 @@ abstract class IndexedElementsBinder extends AggregateBinder { private static final String INDEX_ZERO = "[0]"; IndexedElementsBinder(BindContext context) { - super(context); + super(context, false); } protected final void bindIndexed(ConfigurationPropertyName name, Bindable target, diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java index c7bd816070..358d3db96e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java @@ -41,7 +41,7 @@ class MapBinder extends AggregateBinder> { .mapOf(String.class, String.class); MapBinder(BindContext context) { - super(context); + super(context, true); } @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java index 8779d5738f..7479a8dfaa 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java @@ -351,6 +351,32 @@ public class CollectionBinderTests { assertThat(result.getItemsSet()).containsExactly("a", "b", "c"); } + public void bindToBeanWithNestedCollectionShouldPopulateCollection() + throws Exception { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.value", "one"); + source.put("foo.foos[0].value", "two"); + source.put("foo.foos[1].value", "three"); + this.sources.add(source); + Bindable target = Bindable + .of(BeanWithNestedCollection.class); + BeanWithNestedCollection foo = this.binder.bind("foo", target).get(); + assertThat(foo.getValue()).isNotNull(); + assertThat(foo.getFoos().get(0).getValue()).isEqualTo("two"); + assertThat(foo.getFoos().get(1).getValue()).isEqualTo("three"); + } + + @Test + public void bindToBeanWithNestedCollectionAndNonIterableSourceShouldNotFail() + throws Exception { + // gh-10702 + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + this.sources.add(source.nonIterable()); + Bindable target = Bindable + .of(BeanWithNestedCollection.class); + this.binder.bind("foo", target); + } + public static class ExampleCollectionBean { private List items = new ArrayList<>(); @@ -401,4 +427,27 @@ public class CollectionBinderTests { } + public static class BeanWithNestedCollection { + + private String value; + + private List foos; + + public List getFoos() { + return this.foos; + } + + public void setFoos(List foos) { + this.foos = foos; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + } + }