diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java index cfd887dde2..618c80d034 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java @@ -57,7 +57,7 @@ class CollectionBinder extends IndexedElementsBinder> { @Override protected Collection merge(Supplier> existing, Collection additional) { - Collection existingCollection = existing.get(); + Collection existingCollection = getExistingIfPossible(existing); if (existingCollection == null) { return additional; } @@ -71,6 +71,15 @@ class CollectionBinder extends IndexedElementsBinder> { } } + private Collection getExistingIfPossible(Supplier> existing) { + try { + return existing.get(); + } + catch (Exception ex) { + return null; + } + } + private Collection copyIfPossible(Collection collection) { try { return createNewCollection(collection); 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 01939e7666..29ceb2f253 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 @@ -85,26 +85,45 @@ class MapBinder extends AggregateBinder> { @Override protected Map merge(Supplier> existing, Map additional) { - Map existingMap = existing.get(); + Map existingMap = getExistingIfPossible(existing); if (existingMap == null) { return additional; } - existingMap.putAll(additional); - return copyIfPossible(existingMap); + try { + existingMap.putAll(additional); + return copyIfPossible(existingMap); + } + catch (UnsupportedOperationException ex) { + Map result = createNewMap(additional.getClass(), existingMap); + result.putAll(additional); + return result; + } + } + + private Map getExistingIfPossible(Supplier> existing) { + try { + return existing.get(); + } + catch (Exception ex) { + return null; + } } private Map copyIfPossible(Map map) { try { - Map result = CollectionFactory.createMap(map.getClass(), - map.size()); - result.putAll(map); - return result; + return createNewMap(map.getClass(), map); } catch (Exception ex) { return map; } } + private Map createNewMap(Class mapClass, Map map) { + Map result = CollectionFactory.createMap(mapClass, map.size()); + result.putAll(map); + return result; + } + private class EntryBinder { private final ConfigurationPropertyName root; 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 25dade2c2b..cd057173ef 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 @@ -417,6 +417,16 @@ public class CollectionBinderTests { assertThat(bean.getBar()).containsExactly("hello"); } + @Test + public void bindToBeanWithExceptionInGetterForExistingValue() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.values", "a,b,c"); + this.sources.add(source); + BeanWithGetterException result = this.binder + .bind("foo", Bindable.of(BeanWithGetterException.class)).get(); + assertThat(result.getValues()).containsExactly("a", "b", "c"); + } + public static class ExampleCollectionBean { private List items = new ArrayList<>(); @@ -521,4 +531,18 @@ public class CollectionBinderTests { } + public static class BeanWithGetterException { + + private List values; + + public void setValues(List values) { + this.values = values; + } + + public List getValues() { + return Collections.unmodifiableList(this.values); + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java index 5920a31b3d..6e9607777f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/MapBinderTests.java @@ -615,6 +615,32 @@ public class MapBinderTests { assertThat(result.getItems()).containsExactly(entry("a", "b")); } + @Test + public void bindToImmutableMapShouldReturnPopulatedCollection() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.values.c", "d"); + source.put("foo.values.e", "f"); + this.sources.add(source); + Map result = this.binder + .bind("foo.values", + STRING_STRING_MAP + .withExistingValue(Collections.singletonMap("a", "b"))) + .get(); + assertThat(result).hasSize(3); + assertThat(result.entrySet()).containsExactly(entry("a", "b"), entry("c", "d"), + entry("e", "f")); + } + + @Test + public void bindToBeanWithExceptionInGetterForExistingValue() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.values.a", "b"); + this.sources.add(source); + BeanWithGetterException result = this.binder + .bind("foo", Bindable.of(BeanWithGetterException.class)).get(); + assertThat(result.getValues()).containsExactly(entry("a", "b")); + } + private Bindable> getMapBindable(Class keyGeneric, ResolvableType valueType) { ResolvableType keyType = ResolvableType.forClass(keyGeneric); @@ -720,4 +746,18 @@ public class MapBinderTests { } + public static class BeanWithGetterException { + + private Map values; + + public void setValues(Map values) { + this.values = values; + } + + public Map getValues() { + return Collections.unmodifiableMap(this.values); + } + + } + }