diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index e258563429..ebd5ab0867 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -298,11 +298,10 @@ public class Binder { } BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind( name.append(propertyName), propertyTarget, handler, context); - Class type = target.getType().resolve(); - if (context.hasBoundBean(type)) { + if (context.isProcessingName(name)) { return null; } - return context.withBean(type, () -> { + return context.withName(name, () -> { Stream boundBeans = BEAN_BINDERS.stream() .map((b) -> b.bind(name, target, context, propertyBinder)); return boundBeans.filter(Objects::nonNull).findFirst().orElse(null); @@ -353,7 +352,7 @@ public class Binder { private final List source = Arrays .asList((ConfigurationPropertySource) null); - private final Deque> beans = new ArrayDeque<>(); + private final Deque names = new ArrayDeque<>(); private ConfigurationProperty configurationProperty; @@ -385,13 +384,13 @@ public class Binder { } } - public T withBean(Class bean, Supplier supplier) { - this.beans.push(bean); + public T withName(ConfigurationPropertyName name, Supplier supplier) { + this.names.push(name); try { return withIncreasedDepth(supplier); } finally { - this.beans.pop(); + this.names.pop(); } } @@ -421,8 +420,8 @@ public class Binder { return StreamSupport.stream(Binder.this.sources.spliterator(), false); } - public boolean hasBoundBean(Class bean) { - return this.beans.contains(bean); + public boolean isProcessingName(ConfigurationPropertyName name) { + return this.names.contains(name); } @Override diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java index 78893199a3..777d66398f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java @@ -19,7 +19,9 @@ package org.springframework.boot.context.properties.bind; import java.time.LocalDate; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.assertj.core.matcher.AssertionMatcher; import org.junit.Before; @@ -237,6 +239,19 @@ public class BinderTests { this.binder.bind("foo", target); } + @Test + public void nestedMapsShouldNotBindToNull() throws Exception { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("foo.value", "one"); + source.put("foo.foos.foo1.value", "two"); + source.put("foo.foos.foo2.value", "three"); + this.sources.add(source); + BindResult foo = this.binder.bind("foo", Foo.class); + assertThat(foo.get().getValue()).isNotNull(); + assertThat(foo.get().getFoos().get("foo1").getValue()).isEqualTo("two"); + assertThat(foo.get().getFoos().get("foo2").getValue()).isEqualTo("three"); + } + public static class JavaBean { private String value; @@ -263,4 +278,24 @@ public class BinderTests { } + static class Foo { + + private String value; + + private Map foos = new LinkedHashMap<>(); + + public Map getFoos() { + return this.foos; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + } + }