mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Allow recursive binding in Maps
Update `Binder` so that Maps containing references to themselves may be bound. The existing stack-overflow protection (required when binding a bean to a non enumerable source) now only applies to bean properties. Fixes gh-9801
This commit is contained in:
parent
3ec3b64d45
commit
f337323819
@ -183,18 +183,18 @@ public class Binder {
|
||||
Assert.notNull(target, "Target must not be null");
|
||||
handler = (handler != null ? handler : BindHandler.DEFAULT);
|
||||
Context context = new Context();
|
||||
T bound = bind(name, target, handler, context);
|
||||
T bound = bind(name, target, handler, context, false);
|
||||
return BindResult.of(bound);
|
||||
}
|
||||
|
||||
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context) {
|
||||
BindHandler handler, Context context, boolean skipIfHasBoundBean) {
|
||||
context.clearConfigurationProperty();
|
||||
try {
|
||||
if (!handler.onStart(name, target, context)) {
|
||||
return null;
|
||||
}
|
||||
Object bound = bindObject(name, target, handler, context);
|
||||
Object bound = bindObject(name, target, handler, context, skipIfHasBoundBean);
|
||||
return handleBindResult(name, target, handler, context, bound);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@ -234,7 +234,8 @@ public class Binder {
|
||||
}
|
||||
|
||||
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context) throws Exception {
|
||||
BindHandler handler, Context context, boolean skipIfHasBoundBean)
|
||||
throws Exception {
|
||||
ConfigurationProperty property = findProperty(name, context);
|
||||
if (property == null && containsNoDescendantOf(context.streamSources(), name)) {
|
||||
return null;
|
||||
@ -246,7 +247,7 @@ public class Binder {
|
||||
if (property != null) {
|
||||
return bindProperty(name, target, handler, context, property);
|
||||
}
|
||||
return bindBean(name, target, handler, context);
|
||||
return bindBean(name, target, handler, context, skipIfHasBoundBean);
|
||||
}
|
||||
|
||||
private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) {
|
||||
@ -266,7 +267,8 @@ public class Binder {
|
||||
private <T> Object bindAggregate(ConfigurationPropertyName name, Bindable<T> target,
|
||||
BindHandler handler, Context context, AggregateBinder<?> aggregateBinder) {
|
||||
AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
|
||||
Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context);
|
||||
Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context,
|
||||
false);
|
||||
return context.withSource(source, supplier);
|
||||
};
|
||||
return context.withIncreasedDepth(
|
||||
@ -290,15 +292,16 @@ public class Binder {
|
||||
}
|
||||
|
||||
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
|
||||
BindHandler handler, Context context) {
|
||||
BindHandler handler, Context context, boolean skipIfHasBoundBean) {
|
||||
if (containsNoDescendantOf(context.streamSources(), name)
|
||||
|| isUnbindableBean(name, target, context)) {
|
||||
return null;
|
||||
}
|
||||
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
|
||||
name.append(propertyName), propertyTarget, handler, context);
|
||||
name.append(propertyName), propertyTarget, handler, context, true);
|
||||
Class<?> type = target.getType().resolve();
|
||||
if (context.hasBoundBean(type)) {
|
||||
if (skipIfHasBoundBean && context.hasBoundBean(type)) {
|
||||
System.err.println(type + " " + name);
|
||||
return null;
|
||||
}
|
||||
return context.withBean(type, () -> {
|
||||
|
@ -251,10 +251,12 @@ public class BinderTests {
|
||||
@Test
|
||||
public void bindToValidatedBeanWithResourceAndNonEnumerablePropertySource() {
|
||||
ConfigurationPropertySources.from(new PropertySource<String>("test") {
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}).forEach(this.sources::add);
|
||||
Validator validator = new SpringValidatorAdapter(Validation.byDefaultProvider()
|
||||
.configure().buildValidatorFactory().getValidator());
|
||||
@ -262,6 +264,14 @@ public class BinderTests {
|
||||
new ValidationBindHandler(validator));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindToBeanWithCycle() throws Exception {
|
||||
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||
this.sources.add(source.nonIterable());
|
||||
Bindable<CycleBean1> target = Bindable.of(CycleBean1.class);
|
||||
this.binder.bind("foo", target);
|
||||
}
|
||||
|
||||
public static class JavaBean {
|
||||
|
||||
private String value;
|
||||
@ -303,4 +313,32 @@ public class BinderTests {
|
||||
|
||||
}
|
||||
|
||||
public static class CycleBean1 {
|
||||
|
||||
private CycleBean2 two;
|
||||
|
||||
public CycleBean2 getTwo() {
|
||||
return this.two;
|
||||
}
|
||||
|
||||
public void setTwo(CycleBean2 two) {
|
||||
this.two = two;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class CycleBean2 {
|
||||
|
||||
private CycleBean1 one;
|
||||
|
||||
public CycleBean1 getOne() {
|
||||
return this.one;
|
||||
}
|
||||
|
||||
public void setOne(CycleBean1 one) {
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.springframework.boot.context.properties.bind;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
@ -515,6 +516,19 @@ public class MapBinderTests {
|
||||
assertThat(map).containsEntry("x [B] y", "[ball]");
|
||||
}
|
||||
|
||||
@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<NestableFoo> foo = this.binder.bind("foo", NestableFoo.class);
|
||||
assertThat(foo.get().getValue()).isNotNull();
|
||||
assertThat(foo.get().getFoos().get("foo1").getValue()).isEqualTo("two");
|
||||
assertThat(foo.get().getFoos().get("foo2").getValue()).isEqualTo("three");
|
||||
}
|
||||
|
||||
private <K, V> Bindable<Map<K, V>> getMapBindable(Class<K> keyGeneric,
|
||||
ResolvableType valueType) {
|
||||
ResolvableType keyType = ResolvableType.forClass(keyGeneric);
|
||||
@ -547,4 +561,24 @@ public class MapBinderTests {
|
||||
|
||||
}
|
||||
|
||||
static class NestableFoo {
|
||||
|
||||
private Map<String, NestableFoo> foos = new LinkedHashMap<>();
|
||||
|
||||
private String value;
|
||||
|
||||
public Map<String, NestableFoo> getFoos() {
|
||||
return this.foos;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user