Commit c6dae576 authored by Madhura Bhave's avatar Madhura Bhave Committed by Phillip Webb

Add bindOrCreate for constructor based binding

Deprecate the existing `BindResult.orElseCreate` method in favor of
`bindOrCreate` methods on the `Binder`. These new methods allow us to
implement custom creation logic depending on the type of object being
bound. Specifically, it allows constructor based binding to create new
instances that respect the `@DefaultValue` annotations.

Closes gh-17098
Co-authored-by: 's avatarPhillip Webb <pwebb@pivotal.io>
parent 38fb6391
......@@ -53,8 +53,7 @@ public abstract class PathBasedTemplateAvailabilityProvider implements TemplateA
ResourceLoader resourceLoader) {
if (ClassUtils.isPresent(this.className, classLoader)) {
Binder binder = Binder.get(environment);
TemplateAvailabilityProperties properties = binder.bind(this.propertyPrefix, this.propertiesClass)
.orElseCreate(this.propertiesClass);
TemplateAvailabilityProperties properties = binder.bindOrCreate(this.propertyPrefix, this.propertiesClass);
return isTemplateAvailable(view, resourceLoader, properties);
}
return false;
......
......@@ -55,7 +55,7 @@ final class ConfigurationPropertiesBeanDefinition extends GenericBeanDefinition
ConfigurationPropertiesBinder binder = beanFactory.getBean(ConfigurationPropertiesBinder.BEAN_NAME,
ConfigurationPropertiesBinder.class);
try {
return binder.bind(bindable).orElseCreate(type);
return binder.bindOrCreate(bindable);
}
catch (Exception ex) {
throw new ConfigurationPropertiesBindException(beanName, type, annotation, ex);
......
......@@ -86,11 +86,21 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
}
public <T> BindResult<T> bind(Bindable<T> target) {
ConfigurationProperties annotation = getAnnotation(target);
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
public <T> T bindOrCreate(Bindable<T> target) {
ConfigurationProperties annotation = getAnnotation(target);
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bindOrCreate(annotation.prefix(), target, bindHandler);
}
private <T> ConfigurationProperties getAnnotation(Bindable<?> target) {
ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);
Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target);
List<Validator> validators = getValidators(target);
BindHandler bindHandler = getBindHandler(annotation, validators);
return getBinder().bind(annotation.prefix(), target, bindHandler);
return annotation;
}
private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext,
......@@ -101,6 +111,25 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
return null;
}
private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) {
List<Validator> validators = getValidators(target);
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
}
for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
handler = advisor.apply(handler);
}
return handler;
}
private List<Validator> getValidators(Bindable<?> target) {
List<Validator> validators = new ArrayList<>(3);
if (this.configurationPropertiesValidator != null) {
......@@ -122,24 +151,6 @@ class ConfigurationPropertiesBinder implements ApplicationContextAware {
return this.jsr303Validator;
}
private BindHandler getBindHandler(ConfigurationProperties annotation, List<Validator> validators) {
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
}
for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
handler = advisor.apply(handler);
}
return handler;
}
private List<ConfigurationPropertiesBindHandlerAdvisor> getBindHandlerAdvisors() {
return this.applicationContext.getBeanProvider(ConfigurationPropertiesBindHandlerAdvisor.class).orderedStream()
.collect(Collectors.toList());
......
......@@ -39,4 +39,13 @@ interface BeanBinder {
*/
<T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context, BeanPropertyBinder propertyBinder);
/**
* Return a new instance for the specified type.
* @param type the type used for creating a new instance
* @param context the bind context
* @param <T> the source type
* @return the created instance
*/
<T> T create(Class<T> type, Context context);
}
......@@ -60,10 +60,25 @@ public interface BindHandler {
return result;
}
/**
* Called when binding of an element ends with an unbound result and a newly created
* instance is about to be returned. Implementations may change the ultimately
* returned result or perform addition validation.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
* @param result the newly created instance (never {@code null})
* @return the actual result that should be used (must not be {@code null})
* @since 2.2.2
*/
default Object onCreate(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
return result;
}
/**
* Called when binding fails for any reason (including failures from
* {@link #onSuccess} calls). Implementations may choose to swallow exceptions and
* return an alternative result.
* {@link #onSuccess} or {@link #onCreate} calls). Implementations may choose to
* swallow exceptions and return an alternative result.
* @param name the name of the element being bound
* @param target the item being bound
* @param context the bind context
......
......@@ -118,7 +118,9 @@ public final class BindResult<T> {
* value has been bound.
* @param type the type to create if no value was bound
* @return the value, if bound, otherwise a new instance of {@code type}
* @deprecated since 2.2.0 in favor of {@link Binder#bindOrCreate}
*/
@Deprecated
public T orElseCreate(Class<? extends T> type) {
Assert.notNull(type, "Type must not be null");
return (this.value != null) ? this.value : BeanUtils.instantiateClass(type);
......
......@@ -17,7 +17,6 @@
package org.springframework.boot.context.properties.bind;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
......@@ -25,11 +24,9 @@ import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
......@@ -58,14 +55,7 @@ public class Binder {
private static final Set<Class<?>> NON_BEAN_CLASSES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(Object.class, Class.class)));
private static final List<BeanBinder> BEAN_BINDERS;
static {
List<BeanBinder> binders = new ArrayList<>();
binders.add(new ConstructorParametersBinder());
binders.add(new JavaBeanBinder());
BEAN_BINDERS = Collections.unmodifiableList(binders);
}
private static final BeanBinder[] BEAN_BINDERS = { new ConstructorParametersBinder(), new JavaBeanBinder() };
private final Iterable<ConfigurationPropertySource> sources;
......@@ -196,24 +186,89 @@ public class Binder {
* @return the binding result (never {@code null})
*/
public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
T bound = bind(name, target, handler, false);
return BindResult.of(bound);
}
/**
* Bind the specified target {@link Class} using this binder's
* {@link ConfigurationPropertySource property sources} or create a new instance using
* the type of the {@link Bindable} if the result of the binding is {@code null}.
* @param name the configuration property name to bind
* @param target the target class
* @param <T> the bound type
* @return the bound or created object
* @since 2.2.0
* @see #bind(ConfigurationPropertyName, Bindable, BindHandler)
*/
public <T> T bindOrCreate(String name, Class<T> target) {
return bindOrCreate(name, Bindable.of(target));
}
/**
* Bind the specified target {@link Bindable} using this binder's
* {@link ConfigurationPropertySource property sources} or create a new instance using
* the type of the {@link Bindable} if the result of the binding is {@code null}.
* @param name the configuration property name to bind
* @param target the target bindable
* @param <T> the bound type
* @return the bound or created object
* @since 2.2.0
* @see #bindOrCreate(ConfigurationPropertyName, Bindable, BindHandler)
*/
public <T> T bindOrCreate(String name, Bindable<T> target) {
return bindOrCreate(ConfigurationPropertyName.of(name), target, null);
}
/**
* Bind the specified target {@link Bindable} using this binder's
* {@link ConfigurationPropertySource property sources} or create a new instance using
* the type of the {@link Bindable} if the result of the binding is {@code null}.
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler
* @param <T> the bound type
* @return the bound or created object
* @since 2.2.0
* @see #bindOrCreate(ConfigurationPropertyName, Bindable, BindHandler)
*/
public <T> T bindOrCreate(String name, Bindable<T> target, BindHandler handler) {
return bindOrCreate(ConfigurationPropertyName.of(name), target, handler);
}
/**
* Bind the specified target {@link Bindable} using this binder's
* {@link ConfigurationPropertySource property sources} or create a new instance using
* the type of the {@link Bindable} if the result of the binding is {@code null}.
* @param name the configuration property name to bind
* @param target the target bindable
* @param handler the bind handler (may be {@code null})
* @param <T> the bound or created type
* @since 2.2.0
* @return the bound or created object
*/
public <T> T bindOrCreate(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
return bind(name, target, handler, true);
}
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, boolean create) {
Assert.notNull(name, "Name must not be null");
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, false);
return BindResult.of(bound);
return bind(name, target, handler, context, false, create);
}
protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding) {
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding, boolean create) {
context.clearConfigurationProperty();
try {
target = handler.onStart(name, target, context);
if (target == null) {
return null;
return handleBindResult(name, target, handler, context, null, create);
}
Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
return handleBindResult(name, target, handler, context, bound);
return handleBindResult(name, target, handler, context, bound, create);
}
catch (Exception ex) {
return handleBindError(name, target, handler, context, ex);
......@@ -221,15 +276,32 @@ public class Binder {
}
private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, Object result) throws Exception {
Context context, Object result, boolean create) throws Exception {
if (result != null) {
result = handler.onSuccess(name, target, context, result);
result = context.getConverter().convert(result, target);
}
if (result == null && create) {
result = createBean(target, context);
result = handler.onCreate(name, target, context, result);
result = context.getConverter().convert(result, target);
Assert.state(result != null, () -> "Unable to create instance for " + target.getType());
}
handler.onFinish(name, target, context, result);
return context.getConverter().convert(result, target);
}
private Object createBean(Bindable<?> target, Context context) {
Class<?> type = target.getType().resolve();
for (BeanBinder beanBinder : BEAN_BINDERS) {
Object bean = beanBinder.create(type, context);
if (bean != null) {
return bean;
}
}
return null;
}
private <T> T handleBindError(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, Exception error) {
try {
......@@ -288,7 +360,7 @@ public class Binder {
Context context, AggregateBinder<?> aggregateBinder) {
AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> {
boolean allowRecursiveBinding = aggregateBinder.isAllowRecursiveBinding(source);
Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, allowRecursiveBinding);
Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, allowRecursiveBinding, false);
return context.withSource(source, supplier);
};
return context.withIncreasedDepth(() -> aggregateBinder.bind(name, target, elementBinder));
......@@ -325,10 +397,15 @@ public class Binder {
return null;
}
BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false);
propertyTarget, handler, context, false, false);
return context.withBean(type, () -> {
Stream<?> boundBeans = BEAN_BINDERS.stream().map((b) -> b.bind(name, target, context, propertyBinder));
return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
for (BeanBinder beanBinder : BEAN_BINDERS) {
Object bean = beanBinder.bind(name, target, context, propertyBinder);
if (bean != null) {
return bean;
}
}
return null;
});
}
......
......@@ -57,20 +57,45 @@ class ConstructorParametersBinder implements BeanBinder {
return null;
}
List<Object> bound = bind(propertyBinder, bean, context.getConverter());
return (T) BeanUtils.instantiateClass(bean.getConstructor(), bound.toArray());
return (bound != null) ? (T) BeanUtils.instantiateClass(bean.getConstructor(), bound.toArray()) : null;
}
@Override
@SuppressWarnings("unchecked")
public <T> T create(Class<T> type, Binder.Context context) {
Bean bean = getBean(type);
if (bean == null) {
return null;
}
Collection<ConstructorParameter> parameters = bean.getParameters().values();
List<Object> parameterValues = new ArrayList<>(parameters.size());
for (ConstructorParameter parameter : parameters) {
Object boundParameter = getDefaultValue(parameter, context.getConverter());
parameterValues.add(boundParameter);
}
return (T) BeanUtils.instantiateClass(bean.getConstructor(), parameterValues.toArray());
}
private <T> Bean getBean(Class<T> type) {
if (KOTLIN_PRESENT && KotlinDetector.isKotlinType(type)) {
return KotlinBeanProvider.get(type);
}
return SimpleBeanProvider.get(type);
}
private List<Object> bind(BeanPropertyBinder propertyBinder, Bean bean, BindConverter converter) {
Collection<ConstructorParameter> parameters = bean.getParameters().values();
List<Object> boundParameters = new ArrayList<>(parameters.size());
int unboundParameterCount = 0;
for (ConstructorParameter parameter : parameters) {
Object boundParameter = bind(parameter, propertyBinder);
if (boundParameter == null) {
unboundParameterCount++;
boundParameter = getDefaultValue(parameter, converter);
}
boundParameters.add(boundParameter);
}
return boundParameters;
return (unboundParameterCount != parameters.size()) ? boundParameters : null;
}
private Object getDefaultValue(ConstructorParameter parameter, BindConverter converter) {
......
......@@ -55,6 +55,11 @@ class JavaBeanBinder implements BeanBinder {
return (bound ? beanSupplier.get() : null);
}
@Override
public <T> T create(Class<T> type, Context context) {
return BeanUtils.instantiateClass(type);
}
private boolean hasKnownBindableProperties(ConfigurationPropertyName name, Context context) {
for (ConfigurationPropertySource source : context.getSources()) {
if (source.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT) {
......
......@@ -151,6 +151,8 @@ class BindResultTests {
}
@Test
@Deprecated
@SuppressWarnings("deprecation")
void orElseCreateWhenTypeIsNullShouldThrowException() {
BindResult<String> result = BindResult.of("foo");
assertThatIllegalArgumentException().isThrownBy(() -> result.orElseCreate(null))
......@@ -158,12 +160,14 @@ class BindResultTests {
}
@Test
@Deprecated
void orElseCreateWhenHasValueShouldReturnValue() {
BindResult<ExampleBean> result = BindResult.of(new ExampleBean("foo"));
assertThat(result.orElseCreate(ExampleBean.class).getValue()).isEqualTo("foo");
}
@Test
@Deprecated
void orElseCreateWhenHasValueNoShouldReturnCreatedValue() {
BindResult<ExampleBean> result = BindResult.of(null);
assertThat(result.orElseCreate(ExampleBean.class).getValue()).isEqualTo("new");
......
......@@ -162,6 +162,15 @@ class BinderTests {
ordered.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")), eq(target), any(), eq(1));
}
@Test
void bindOrCreateWhenNotBoundShouldTriggerOnCreate() {
BindHandler handler = mock(BindHandler.class, Answers.CALLS_REAL_METHODS);
Bindable<JavaBean> target = Bindable.of(JavaBean.class);
this.binder.bindOrCreate("foo", target, handler);
InOrder ordered = inOrder(handler);
ordered.verify(handler).onCreate(eq(ConfigurationPropertyName.of("foo")), eq(target), any(), any());
}
@Test
void bindToJavaBeanShouldReturnPopulatedBean() {
this.sources.add(new MockConfigurationPropertySource("foo.value", "bar"));
......@@ -280,6 +289,21 @@ class BinderTests {
assertThat(result.getValue()).isEqualTo("hello");
}
@Test
void bindOrCreateWhenBindSuccessfulShouldReturnBoundValue() {
this.sources.add(new MockConfigurationPropertySource("foo.value", "bar"));
JavaBean result = this.binder.bindOrCreate("foo", Bindable.of(JavaBean.class));
assertThat(result.getValue()).isEqualTo("bar");
assertThat(result.getItems()).isEmpty();
}
@Test
void bindOrCreateWhenUnboundShouldReturnCreatedValue() {
JavaBean value = this.binder.bindOrCreate("foo", Bindable.of(JavaBean.class));
assertThat(value).isNotNull();
assertThat(value).isInstanceOf(JavaBean.class);
}
public static class JavaBean {
private String value;
......@@ -300,6 +324,40 @@ class BinderTests {
}
public static class NestedJavaBean {
private DefaultValuesBean valuesBean = new DefaultValuesBean();
public DefaultValuesBean getValuesBean() {
return this.valuesBean;
}
public void setValuesBean(DefaultValuesBean valuesBean) {
this.valuesBean = valuesBean;
}
}
public static class DefaultValuesBean {
private String value = "hello";
private List<String> items = Collections.emptyList();
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
public List<String> getItems() {
return this.items;
}
}
public enum ExampleEnum {
FOO_BAR, BAR_BAZ, BAZ_BOO
......
......@@ -18,6 +18,7 @@ package org.springframework.boot.context.properties.bind;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.junit.jupiter.api.Test;
......@@ -129,14 +130,20 @@ class ConstructorParametersBinderTests {
}
@Test
void bindToClassWithNoValueAndDefaultValueShouldUseDefault() {
void bindToClassWithNoValueAndDefaultValueShouldNotBind() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.string-value", "foo");
this.sources.add(source);
ExampleDefaultValueBean bean = this.binder.bind("foo", Bindable.of(ExampleDefaultValueBean.class)).get();
assertThat(bean.getIntValue()).isEqualTo(5);
assertThat(bean.getStringsList()).containsOnly("a", "b", "c");
assertThat(bean.getCustomList()).containsOnly("x,y,z");
assertThat(this.binder.bind("foo", Bindable.of(ExampleDefaultValueBean.class)).isBound()).isFalse();
}
@Test
void bindToClassWhenNoParameterBoundShouldReturnNull() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
this.sources.add(source.nonIterable());
BindResult<ExampleFailingConstructorBean> result = this.binder.bind("foo",
Bindable.of(ExampleFailingConstructorBean.class));
assertThat(result.isBound()).isFalse();
}
@Test
......@@ -149,6 +156,47 @@ class ConstructorParametersBinderTests {
assertThat(bean.getDate().toString()).isEqualTo("2014-04-01");
}
@Test
void bindWithAnnotationsAndDefaultValue() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "hello");
this.sources.add(source);
ConverterAnnotatedExampleBean bean = this.binder.bind("foo", Bindable.of(ConverterAnnotatedExampleBean.class))
.get();
assertThat(bean.getDate().toString()).isEqualTo("2019-05-10");
}
@Test
void createShouldReturnCreatedValue() {
ExampleValueBean value = this.binder.bindOrCreate("foo", Bindable.of(ExampleValueBean.class));
assertThat(value.getIntValue()).isEqualTo(0);
assertThat(value.getLongValue()).isEqualTo(0);
assertThat(value.isBooleanValue()).isEqualTo(false);
assertThat(value.getStringValue()).isNull();
assertThat(value.getEnumValue()).isNull();
}
@Test
void createWithNestedShouldReturnCreatedValue() {
ExampleNestedBean value = this.binder.bindOrCreate("foo", Bindable.of(ExampleNestedBean.class));
assertThat(value.getValueBean()).isEqualTo(null);
}
@Test
void createWithDefaultValuesShouldReturnCreatedWithDefaultValues() {
ExampleDefaultValueBean value = this.binder.bindOrCreate("foo", Bindable.of(ExampleDefaultValueBean.class));
assertThat(value.getIntValue()).isEqualTo(5);
assertThat(value.getStringsList()).containsOnly("a", "b", "c");
assertThat(value.getCustomList()).containsOnly("x,y,z");
}
@Test
void createWithDefaultValuesAndAnnotationsShouldReturnCreatedWithDefaultValues() {
ConverterAnnotatedExampleBean bean = this.binder.bindOrCreate("foo",
Bindable.of(ConverterAnnotatedExampleBean.class));
assertThat(bean.getDate().toString()).isEqualTo("2019-05-10");
}
public static class ExampleValueBean {
private final int intValue;
......@@ -277,18 +325,49 @@ class ConstructorParametersBinderTests {
}
public static class ExampleFailingConstructorBean {
private final String name;
private final Object value;
ExampleFailingConstructorBean(String name, String value) {
Objects.requireNonNull(name, "'name' must be not null.");
Objects.requireNonNull(value, "'value' must be not null.");
this.name = name;
this.value = value;
}
public String getName() {
return this.name;
}
public Object getValue() {
return this.value;
}
}
public static class ConverterAnnotatedExampleBean {
private final LocalDate date;
ConverterAnnotatedExampleBean(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {
private final String bar;
ConverterAnnotatedExampleBean(
@DefaultValue("2019-05-10") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, String bar) {
this.date = date;
this.bar = bar;
}
public LocalDate getDate() {
return this.date;
}
public String getBar() {
return this.bar;
}
}
}
......@@ -523,6 +523,12 @@ class JavaBeanBinderTests {
property.setValue(() -> target, "some string");
}
@Test
void bindOrCreateWithNestedShouldReturnCreatedValue() {
NestedJavaBean result = this.binder.bindOrCreate("foo", Bindable.of(NestedJavaBean.class));
assertThat(result.getNested().getBar()).isEqualTo(456);
}
public static class ExampleValueBean {
private int intValue;
......@@ -991,4 +997,18 @@ class JavaBeanBinderTests {
}
public static class NestedJavaBean {
private ExampleDefaultsBean nested = new ExampleDefaultsBean();
public ExampleDefaultsBean getNested() {
return this.nested;
}
public void setNested(ExampleDefaultsBean nested) {
this.nested = nested;
}
}
}
......@@ -140,12 +140,21 @@ class KotlinConstructorParametersBinderTests {
}
@Test
fun `Bind to class with no value and default value should use default value`() {
fun `Bind to class with no value and default value should return unbound`() {
val source = MockConfigurationPropertySource()
source.put("foo.string-value", "foo")
val binder = Binder(source)
val bean = binder.bind("foo", Bindable.of(
ExampleDefaultValueBean::class.java)).get()
assertThat(binder.bind("foo", Bindable.of(
ExampleDefaultValueBean::class.java)).isBound()).isFalse();
}
@Test
fun `Bind or create to class with no value and default value should return default value`() {
val source = MockConfigurationPropertySource()
source.put("foo.string-value", "foo")
val binder = Binder(source)
val bean = binder.bindOrCreate("foo", Bindable.of(
ExampleDefaultValueBean::class.java))
assertThat(bean.intValue).isEqualTo(5)
assertThat(bean.stringsList).containsOnly("a", "b", "c")
assertThat(bean.customList).containsOnly("x,y,z")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment