diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java index 29c7540084..89cffae019 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java @@ -19,7 +19,6 @@ package org.springframework.boot.context.properties.bind; import java.util.function.Supplier; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; -import org.springframework.core.ResolvableType; /** * Internal strategy used by {@link Binder} to bind aggregates (Maps, Lists, Arrays). @@ -47,9 +46,7 @@ abstract class AggregateBinder { public final Object bind(ConfigurationPropertyName name, Bindable target, AggregateElementBinder itemBinder) { Supplier value = target.getValue(); - Class type = (value == null ? target.getType().resolve() - : ResolvableType.forClass(AggregateBinder.class, getClass()) - .resolveGeneric()); + Class type = (value == null ? target.getType().resolve() : null); Object result = bind(name, target, itemBinder, type); if (result == null || value == null || value.get() == null) { return result; diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java index 8889347e16..91ebc662ca 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java @@ -17,6 +17,7 @@ package org.springframework.boot.context.properties.bind; import java.util.Collection; +import java.util.List; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.core.CollectionFactory; @@ -37,10 +38,12 @@ class CollectionBinder extends IndexedElementsBinder> { @Override protected Object bind(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder, Class type) { + Class collectionType = (type != null ? type + : ResolvableType.forClassWithGenerics(List.class, Object.class).resolve()); IndexedCollectionSupplier collection = new IndexedCollectionSupplier( - () -> CollectionFactory.createCollection(type, 0)); + () -> CollectionFactory.createCollection(collectionType, 0)); ResolvableType elementType = target.getType().asCollection().getGeneric(); - bindIndexed(name, target, elementBinder, collection, target.getType(), + bindIndexed(name, target, elementBinder, collection, ResolvableType.forClass(collectionType), elementType); if (collection.wasSupplied()) { return collection.get(); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java index 42bc614637..d7e1b499de 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java @@ -48,7 +48,9 @@ class MapBinder extends AggregateBinder> { @Override protected Object bind(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder, Class type) { - Map map = CollectionFactory.createMap(type, 0); + Class mapType = (type != null ? type + : ResolvableType.forClassWithGenerics(Map.class, Object.class, Object.class).resolve()); + Map map = CollectionFactory.createMap(mapType, 0); Bindable resolvedTarget = resolveTarget(target); for (ConfigurationPropertySource source : getContext().getSources()) { if (!ConfigurationPropertyName.EMPTY.equals(name)) { diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java index 474bc57738..8967363ca5 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.context.properties.bind; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -316,10 +317,45 @@ public class CollectionBinderTests { assertThat(result.getItems()).containsExactly("a", "b", "c", "d"); } + @Test + public void bindToCollectionWithNoDefaultConstructor() throws Exception { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.items", "a,b,c,c"); + this.sources.add(source); + ExampleCustomBean result = this.binder + .bind("foo", ExampleCustomBean.class).get(); + assertThat(result.getItems()).hasSize(4); + assertThat(result.getItems()).containsExactly("a", "b", "c", "c"); + } + + @Test + public void bindToListShouldAllowDuplicateValues() throws Exception { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.items", "a,b,c,c"); + this.sources.add(source); + ExampleCollectionBean result = this.binder + .bind("foo", ExampleCollectionBean.class).get(); + assertThat(result.getItems()).hasSize(5); + assertThat(result.getItems()).containsExactly("a", "b", "c", "c", "d"); + } + + @Test + public void bindToSetShouldNotAllowDuplicateValues() throws Exception { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.items-set", "a,b,c,c"); + this.sources.add(source); + ExampleCollectionBean result = this.binder + .bind("foo", ExampleCollectionBean.class).get(); + assertThat(result.getItemsSet()).hasSize(3); + assertThat(result.getItemsSet()).containsExactly("a", "b", "c"); + } + public static class ExampleCollectionBean { private List items = new ArrayList<>(); + private Set itemsSet = new HashSet<>(); + public List getItems() { return this.items; } @@ -328,6 +364,36 @@ public class CollectionBinderTests { this.items.add("d"); } + public Set getItemsSet() { + return this.itemsSet; + } + + public void setItemsSet(Set itemsSet) { + this.itemsSet = itemsSet; + } + } + + public static class ExampleCustomBean { + + private MyCustomList items = new MyCustomList(Collections.singletonList("foo")); + + public MyCustomList getItems() { + return this.items; + } + + public void setItems(MyCustomList items) { + this.items = items; + } + } + + public static class MyCustomList extends ArrayList { + + private List items = new ArrayList<>(Collections.singletonList("foo")); + + public MyCustomList(List items) { + this.items = items; + } + } }