Skip to content

Commit 4a740a1

Browse files
committed
Duplicate values should bind properly to List
Fixes gh-10106
1 parent 0f25dd0 commit 4a740a1

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed

spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.function.Supplier;
2020

2121
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
22-
import org.springframework.core.ResolvableType;
2322

2423
/**
2524
* Internal strategy used by {@link Binder} to bind aggregates (Maps, Lists, Arrays).
@@ -47,9 +46,7 @@ abstract class AggregateBinder<T> {
4746
public final Object bind(ConfigurationPropertyName name, Bindable<?> target,
4847
AggregateElementBinder itemBinder) {
4948
Supplier<?> value = target.getValue();
50-
Class<?> type = (value == null ? target.getType().resolve()
51-
: ResolvableType.forClass(AggregateBinder.class, getClass())
52-
.resolveGeneric());
49+
Class<?> type = (value == null ? target.getType().resolve() : null);
5350
Object result = bind(name, target, itemBinder, type);
5451
if (result == null || value == null || value.get() == null) {
5552
return result;

spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.context.properties.bind;
1818

1919
import java.util.Collection;
20+
import java.util.List;
2021

2122
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
2223
import org.springframework.core.CollectionFactory;
@@ -37,10 +38,12 @@ class CollectionBinder extends IndexedElementsBinder<Collection<Object>> {
3738
@Override
3839
protected Object bind(ConfigurationPropertyName name, Bindable<?> target,
3940
AggregateElementBinder elementBinder, Class<?> type) {
41+
Class<?> collectionType = (type != null ? type
42+
: ResolvableType.forClassWithGenerics(List.class, Object.class).resolve());
4043
IndexedCollectionSupplier collection = new IndexedCollectionSupplier(
41-
() -> CollectionFactory.createCollection(type, 0));
44+
() -> CollectionFactory.createCollection(collectionType, 0));
4245
ResolvableType elementType = target.getType().asCollection().getGeneric();
43-
bindIndexed(name, target, elementBinder, collection, target.getType(),
46+
bindIndexed(name, target, elementBinder, collection, ResolvableType.forClass(collectionType),
4447
elementType);
4548
if (collection.wasSupplied()) {
4649
return collection.get();

spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
4848
@Override
4949
protected Object bind(ConfigurationPropertyName name, Bindable<?> target,
5050
AggregateElementBinder elementBinder, Class<?> type) {
51-
Map<Object, Object> map = CollectionFactory.createMap(type, 0);
51+
Class<?> mapType = (type != null ? type
52+
: ResolvableType.forClassWithGenerics(Map.class, Object.class, Object.class).resolve());
53+
Map<Object, Object> map = CollectionFactory.createMap(mapType, 0);
5254
Bindable<?> resolvedTarget = resolveTarget(target);
5355
for (ConfigurationPropertySource source : getContext().getSources()) {
5456
if (!ConfigurationPropertyName.EMPTY.equals(name)) {

spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.HashSet;
2122
import java.util.LinkedList;
2223
import java.util.List;
2324
import java.util.Set;
@@ -316,10 +317,45 @@ public void bindToCollectionShouldAlsoCallSetterIfPresent() throws Exception {
316317
assertThat(result.getItems()).containsExactly("a", "b", "c", "d");
317318
}
318319

320+
@Test
321+
public void bindToCollectionWithNoDefaultConstructor() throws Exception {
322+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
323+
source.put("foo.items", "a,b,c,c");
324+
this.sources.add(source);
325+
ExampleCustomBean result = this.binder
326+
.bind("foo", ExampleCustomBean.class).get();
327+
assertThat(result.getItems()).hasSize(4);
328+
assertThat(result.getItems()).containsExactly("a", "b", "c", "c");
329+
}
330+
331+
@Test
332+
public void bindToListShouldAllowDuplicateValues() throws Exception {
333+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
334+
source.put("foo.items", "a,b,c,c");
335+
this.sources.add(source);
336+
ExampleCollectionBean result = this.binder
337+
.bind("foo", ExampleCollectionBean.class).get();
338+
assertThat(result.getItems()).hasSize(5);
339+
assertThat(result.getItems()).containsExactly("a", "b", "c", "c", "d");
340+
}
341+
342+
@Test
343+
public void bindToSetShouldNotAllowDuplicateValues() throws Exception {
344+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
345+
source.put("foo.items-set", "a,b,c,c");
346+
this.sources.add(source);
347+
ExampleCollectionBean result = this.binder
348+
.bind("foo", ExampleCollectionBean.class).get();
349+
assertThat(result.getItemsSet()).hasSize(3);
350+
assertThat(result.getItemsSet()).containsExactly("a", "b", "c");
351+
}
352+
319353
public static class ExampleCollectionBean {
320354

321355
private List<String> items = new ArrayList<>();
322356

357+
private Set<String> itemsSet = new HashSet<>();
358+
323359
public List<String> getItems() {
324360
return this.items;
325361
}
@@ -328,6 +364,36 @@ public void setItems(List<String> items) {
328364
this.items.add("d");
329365
}
330366

367+
public Set<String> getItemsSet() {
368+
return this.itemsSet;
369+
}
370+
371+
public void setItemsSet(Set<String> itemsSet) {
372+
this.itemsSet = itemsSet;
373+
}
374+
}
375+
376+
public static class ExampleCustomBean {
377+
378+
private MyCustomList items = new MyCustomList(Collections.singletonList("foo"));
379+
380+
public MyCustomList getItems() {
381+
return this.items;
382+
}
383+
384+
public void setItems(MyCustomList items) {
385+
this.items = items;
386+
}
387+
}
388+
389+
public static class MyCustomList extends ArrayList {
390+
391+
private List<String> items = new ArrayList<>(Collections.singletonList("foo"));
392+
393+
public MyCustomList(List<String> items) {
394+
this.items = items;
395+
}
396+
331397
}
332398

333399
}

0 commit comments

Comments
 (0)