Merge branch '3.0.x'

Closes gh-35644
pull/35655/head
Andy Wilkinson 2 years ago
commit 75f55cc35a

@ -18,7 +18,7 @@ package org.springframework.boot.context.properties;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.AttributeAccessor; import org.springframework.core.AttributeAccessor;

@ -61,19 +61,22 @@ import org.springframework.validation.annotation.Validated;
*/ */
public final class ConfigurationPropertiesBean { public final class ConfigurationPropertiesBean {
private static final org.springframework.boot.context.properties.bind.BindMethod JAVA_BEAN_BIND_METHOD = //
org.springframework.boot.context.properties.bind.BindMethod.JAVA_BEAN;
private static final org.springframework.boot.context.properties.bind.BindMethod VALUE_OBJECT_BIND_METHOD = //
org.springframework.boot.context.properties.bind.BindMethod.VALUE_OBJECT;
private final String name; private final String name;
private final Object instance; private final Object instance;
private final Bindable<?> bindTarget; private final Bindable<?> bindTarget;
private final BindMethod bindMethod; private ConfigurationPropertiesBean(String name, Object instance, Bindable<?> bindTarget) {
private ConfigurationPropertiesBean(String name, Object instance, Bindable<?> bindTarget, BindMethod bindMethod) {
this.name = name; this.name = name;
this.instance = instance; this.instance = instance;
this.bindTarget = bindTarget; this.bindTarget = bindTarget;
this.bindMethod = (bindMethod != null) ? bindMethod : BindMethod.get(bindTarget);
} }
/** /**
@ -102,10 +105,13 @@ public final class ConfigurationPropertiesBean {
/** /**
* Return the property binding method that was used for the bean. * Return the property binding method that was used for the bean.
* @return the bind type * @return the bind method
* @deprecated since 3.0.8 for removal in 3.3.0 in favor of {@link #asBindTarget} and
* {@link Bindable#getBindMethod}
*/ */
@Deprecated(since = "3.0.8", forRemoval = true)
public BindMethod getBindMethod() { public BindMethod getBindMethod() {
return this.bindMethod; return BindMethod.from(this.bindTarget.getBindMethod());
} }
/** /**
@ -206,14 +212,17 @@ public final class ConfigurationPropertiesBean {
if (bindTarget == null) { if (bindTarget == null) {
return null; return null;
} }
BindMethod bindMethod = BindMethodAttribute.get(applicationContext, beanName); bindTarget = bindTarget.withBindMethod(BindMethodAttribute.get(applicationContext, beanName));
if (bindMethod == null && factoryMethod != null) { if (bindTarget.getBindMethod() == null && factoryMethod != null) {
bindMethod = BindMethod.JAVA_BEAN; bindTarget = bindTarget.withBindMethod(JAVA_BEAN_BIND_METHOD);
} }
if (bindTarget != null && bindMethod != BindMethod.VALUE_OBJECT) { if (bindTarget.getBindMethod() == null) {
bindTarget = bindTarget.withBindMethod(deduceBindMethod(bindTarget));
}
if (bindTarget.getBindMethod() != VALUE_OBJECT_BIND_METHOD) {
bindTarget = bindTarget.withExistingValue(bean); bindTarget = bindTarget.withExistingValue(bean);
} }
return create(beanName, bean, bindTarget, bindMethod); return create(beanName, bean, bindTarget);
} }
private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) { private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
@ -263,10 +272,9 @@ public final class ConfigurationPropertiesBean {
static ConfigurationPropertiesBean forValueObject(Class<?> beanType, String beanName) { static ConfigurationPropertiesBean forValueObject(Class<?> beanType, String beanName) {
Bindable<Object> bindTarget = createBindTarget(null, beanType, null); Bindable<Object> bindTarget = createBindTarget(null, beanType, null);
ConfigurationPropertiesBean propertiesBean = create(beanName, null, bindTarget, null); Assert.state(bindTarget != null && deduceBindMethod(bindTarget) == VALUE_OBJECT_BIND_METHOD,
Assert.state(propertiesBean != null && propertiesBean.getBindMethod() == BindMethod.VALUE_OBJECT,
() -> "Bean '" + beanName + "' is not a @ConfigurationProperties value object"); () -> "Bean '" + beanName + "' is not a @ConfigurationProperties value object");
return propertiesBean; return create(beanName, null, bindTarget.withBindMethod(VALUE_OBJECT_BIND_METHOD));
} }
private static Bindable<Object> createBindTarget(Object bean, Class<?> beanType, Method factoryMethod) { private static Bindable<Object> createBindTarget(Object bean, Class<?> beanType, Method factoryMethod) {
@ -307,14 +315,40 @@ public final class ConfigurationPropertiesBean {
: MergedAnnotation.missing(); : MergedAnnotation.missing();
} }
private static ConfigurationPropertiesBean create(String name, Object instance, Bindable<Object> bindTarget, private static ConfigurationPropertiesBean create(String name, Object instance, Bindable<Object> bindTarget) {
BindMethod bindMethod) { return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget) : null;
return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget, bindMethod) : null; }
/**
* Deduce the {@code BindMethod} that should be used for the given type.
* @param type the source type
* @return the bind method to use
*/
static org.springframework.boot.context.properties.bind.BindMethod deduceBindMethod(Class<?> type) {
return deduceBindMethod(BindConstructorProvider.DEFAULT.getBindConstructor(type, false));
}
/**
* Deduce the {@code BindMethod} that should be used for the given {@link Bindable}.
* @param bindable the source bindable
* @return the bind method to use
*/
static org.springframework.boot.context.properties.bind.BindMethod deduceBindMethod(Bindable<Object> bindable) {
return deduceBindMethod(BindConstructorProvider.DEFAULT.getBindConstructor(bindable, false));
}
private static org.springframework.boot.context.properties.bind.BindMethod deduceBindMethod(
Constructor<?> bindConstructor) {
return (bindConstructor != null) ? VALUE_OBJECT_BIND_METHOD : JAVA_BEAN_BIND_METHOD;
} }
/** /**
* The binding method that is used for the bean. * The binding method that is used for the bean.
*
* @deprecated since 3.0.8 for removal in 3.3.0 in favor of
* {@link org.springframework.boot.context.properties.bind.BindMethod}
*/ */
@Deprecated(since = "3.0.8", forRemoval = true)
public enum BindMethod { public enum BindMethod {
/** /**
@ -327,16 +361,14 @@ public final class ConfigurationPropertiesBean {
*/ */
VALUE_OBJECT; VALUE_OBJECT;
static BindMethod get(Class<?> type) { static BindMethod from(org.springframework.boot.context.properties.bind.BindMethod bindMethod) {
return get(BindConstructorProvider.DEFAULT.getBindConstructor(type, false)); if (bindMethod == null) {
} return null;
}
static BindMethod get(Bindable<?> bindable) { return switch (bindMethod) {
return get(BindConstructorProvider.DEFAULT.getBindConstructor(bindable, false)); case VALUE_OBJECT -> BindMethod.VALUE_OBJECT;
} case JAVA_BEAN -> BindMethod.JAVA_BEAN;
};
private static BindMethod get(Constructor<?> bindConstructor) {
return (bindConstructor != null) ? VALUE_OBJECT : JAVA_BEAN;
} }
} }

@ -22,7 +22,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
@ -90,7 +90,7 @@ final class ConfigurationPropertiesBeanRegistrar {
} }
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) { private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
BindMethod bindMethod = BindMethod.get(type); BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(type);
RootBeanDefinition definition = new RootBeanDefinition(type); RootBeanDefinition definition = new RootBeanDefinition(type);
BindMethodAttribute.set(definition, bindMethod); BindMethodAttribute.set(definition, bindMethod);
if (bindMethod == BindMethod.VALUE_OBJECT) { if (bindMethod == BindMethod.VALUE_OBJECT) {

@ -33,7 +33,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
/** /**

@ -22,7 +22,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -89,8 +89,9 @@ public class ConfigurationPropertiesBindingPostProcessor
if (bean == null) { if (bean == null) {
return; return;
} }
Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '" Assert.state(bean.asBindTarget().getBindMethod() != BindMethod.VALUE_OBJECT,
+ bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean"); "Cannot bind @ConfigurationProperties for bean '" + bean.getName()
+ "'. Ensure that @ConstructorBinding has not been applied to regular bean");
try { try {
this.binder.bind(bean); this.binder.bind(bean);
} }

@ -21,13 +21,13 @@ import java.lang.reflect.Constructor;
import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAnalyzer; import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAnalyzer;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
/** /**
* An {@link AbstractInjectionFailureAnalyzer} for * An {@link AbstractInjectionFailureAnalyzer} for
@ -61,14 +61,15 @@ class NotConstructorBoundInjectionFailureAnalyzer
} }
private boolean isConstructorBindingConfigurationProperties(InjectionPoint injectionPoint) { private boolean isConstructorBindingConfigurationProperties(InjectionPoint injectionPoint) {
if (injectionPoint != null && injectionPoint.getMember() instanceof Constructor<?> constructor) { return (injectionPoint != null && injectionPoint.getMember() instanceof Constructor<?> constructor)
Class<?> declaringClass = constructor.getDeclaringClass(); ? isConstructorBindingConfigurationProperties(constructor) : false;
MergedAnnotation<ConfigurationProperties> configurationProperties = MergedAnnotations.from(declaringClass) }
.get(ConfigurationProperties.class);
return configurationProperties.isPresent() private boolean isConstructorBindingConfigurationProperties(Constructor<?> constructor) {
&& BindMethod.get(constructor.getDeclaringClass()) == BindMethod.VALUE_OBJECT; Class<?> declaringClass = constructor.getDeclaringClass();
} BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(declaringClass);
return false; return MergedAnnotations.from(declaringClass, SearchStrategy.TYPE_HIERARCHY)
.isPresent(ConfigurationProperties.class) && bindMethod == BindMethod.VALUE_OBJECT;
} }
private InjectionPoint findInjectionPoint(Throwable failure) { private InjectionPoint findInjectionPoint(Throwable failure) {

@ -0,0 +1,37 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
/**
* Configuration property binding methods.
*
* @author Andy Wilkinson
* @since 3.0.8
*/
public enum BindMethod {
/**
* Java Bean using getter/setter binding.
*/
JAVA_BEAN,
/**
* Value object using constructor binding.
*/
VALUE_OBJECT;
}

@ -57,13 +57,16 @@ public final class Bindable<T> {
private final EnumSet<BindRestriction> bindRestrictions; private final EnumSet<BindRestriction> bindRestrictions;
private final BindMethod bindMethod;
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations, private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations,
EnumSet<BindRestriction> bindRestrictions) { EnumSet<BindRestriction> bindRestrictions, BindMethod bindMethod) {
this.type = type; this.type = type;
this.boxedType = boxedType; this.boxedType = boxedType;
this.value = value; this.value = value;
this.annotations = annotations; this.annotations = annotations;
this.bindRestrictions = bindRestrictions; this.bindRestrictions = bindRestrictions;
this.bindMethod = bindMethod;
} }
/** /**
@ -124,6 +127,16 @@ public final class Bindable<T> {
return this.bindRestrictions.contains(bindRestriction); return this.bindRestrictions.contains(bindRestriction);
} }
/**
* Returns the {@link BindMethod method} to be used to bind this bindable, or
* {@code null} if no specific binding method is required.
* @return the bind method or {@code null}
* @since 3.0.8
*/
public BindMethod getBindMethod() {
return this.bindMethod;
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
@ -137,6 +150,7 @@ public final class Bindable<T> {
result = result && nullSafeEquals(this.type.resolve(), other.type.resolve()); result = result && nullSafeEquals(this.type.resolve(), other.type.resolve());
result = result && nullSafeEquals(this.annotations, other.annotations); result = result && nullSafeEquals(this.annotations, other.annotations);
result = result && nullSafeEquals(this.bindRestrictions, other.bindRestrictions); result = result && nullSafeEquals(this.bindRestrictions, other.bindRestrictions);
result = result && nullSafeEquals(this.bindMethod, other.bindMethod);
return result; return result;
} }
@ -147,6 +161,7 @@ public final class Bindable<T> {
result = prime * result + ObjectUtils.nullSafeHashCode(this.type); result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations); result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
result = prime * result + ObjectUtils.nullSafeHashCode(this.bindRestrictions); result = prime * result + ObjectUtils.nullSafeHashCode(this.bindRestrictions);
result = prime * result + ObjectUtils.nullSafeHashCode(this.bindMethod);
return result; return result;
} }
@ -156,6 +171,7 @@ public final class Bindable<T> {
creator.append("type", this.type); creator.append("type", this.type);
creator.append("value", (this.value != null) ? "provided" : "none"); creator.append("value", (this.value != null) ? "provided" : "none");
creator.append("annotations", this.annotations); creator.append("annotations", this.annotations);
creator.append("bindMethod", this.bindMethod);
return creator.toString(); return creator.toString();
} }
@ -170,11 +186,12 @@ public final class Bindable<T> {
*/ */
public Bindable<T> withAnnotations(Annotation... annotations) { public Bindable<T> withAnnotations(Annotation... annotations) {
return new Bindable<>(this.type, this.boxedType, this.value, return new Bindable<>(this.type, this.boxedType, this.value,
(annotations != null) ? annotations : NO_ANNOTATIONS, NO_BIND_RESTRICTIONS); (annotations != null) ? annotations : NO_ANNOTATIONS, NO_BIND_RESTRICTIONS, this.bindMethod);
} }
/** /**
* Create an updated {@link Bindable} instance with an existing value. * Create an updated {@link Bindable} instance with an existing value. Implies that
* Java Bean binding will be used.
* @param existingValue the existing value * @param existingValue the existing value
* @return an updated {@link Bindable} * @return an updated {@link Bindable}
*/ */
@ -182,8 +199,11 @@ public final class Bindable<T> {
Assert.isTrue( Assert.isTrue(
existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue), existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue),
() -> "ExistingValue must be an instance of " + this.type); () -> "ExistingValue must be an instance of " + this.type);
Assert.state(this.bindMethod != BindMethod.VALUE_OBJECT,
() -> "An existing value cannot be provided when binding as a value object");
Supplier<T> value = (existingValue != null) ? () -> existingValue : null; Supplier<T> value = (existingValue != null) ? () -> existingValue : null;
return new Bindable<>(this.type, this.boxedType, value, this.annotations, this.bindRestrictions); return new Bindable<>(this.type, this.boxedType, value, this.annotations, this.bindRestrictions,
BindMethod.JAVA_BEAN);
} }
/** /**
@ -192,7 +212,8 @@ public final class Bindable<T> {
* @return an updated {@link Bindable} * @return an updated {@link Bindable}
*/ */
public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) { public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) {
return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations, this.bindRestrictions); return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations, this.bindRestrictions,
this.bindMethod);
} }
/** /**
@ -204,7 +225,23 @@ public final class Bindable<T> {
public Bindable<T> withBindRestrictions(BindRestriction... additionalRestrictions) { public Bindable<T> withBindRestrictions(BindRestriction... additionalRestrictions) {
EnumSet<BindRestriction> bindRestrictions = EnumSet.copyOf(this.bindRestrictions); EnumSet<BindRestriction> bindRestrictions = EnumSet.copyOf(this.bindRestrictions);
bindRestrictions.addAll(Arrays.asList(additionalRestrictions)); bindRestrictions.addAll(Arrays.asList(additionalRestrictions));
return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, bindRestrictions); return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, bindRestrictions,
this.bindMethod);
}
/**
* Create an updated {@link Bindable} instance with a specifc bind method. To use
* {@link BindMethod#VALUE_OBJECT value object binding}, the current instance must not
* have an existing or supplied value.
* @param bindMethod the method to use to bind the bindable
* @return an updated {@link Bindable}
* @since 3.0.8
*/
public Bindable<T> withBindMethod(BindMethod bindMethod) {
Assert.state(bindMethod != BindMethod.VALUE_OBJECT || this.value == null,
() -> "Value object binding cannot be used with an existing or supplied value");
return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, this.bindRestrictions,
bindMethod);
} }
/** /**
@ -277,7 +314,7 @@ public final class Bindable<T> {
public static <T> Bindable<T> of(ResolvableType type) { public static <T> Bindable<T> of(ResolvableType type) {
Assert.notNull(type, "Type must not be null"); Assert.notNull(type, "Type must not be null");
ResolvableType boxedType = box(type); ResolvableType boxedType = box(type);
return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS, NO_BIND_RESTRICTIONS); return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS, NO_BIND_RESTRICTIONS, null);
} }
private static ResolvableType box(ResolvableType type) { private static ResolvableType box(ResolvableType type) {

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -64,7 +65,7 @@ public class Binder {
private final BindHandler defaultBindHandler; private final BindHandler defaultBindHandler;
private final List<DataObjectBinder> dataObjectBinders; private final Map<BindMethod, List<DataObjectBinder>> dataObjectBinders;
/** /**
* Create a new {@link Binder} instance for the specified sources. A * Create a new {@link Binder} instance for the specified sources. A
@ -194,7 +195,11 @@ public class Binder {
} }
ValueObjectBinder valueObjectBinder = new ValueObjectBinder(constructorProvider); ValueObjectBinder valueObjectBinder = new ValueObjectBinder(constructorProvider);
JavaBeanBinder javaBeanBinder = JavaBeanBinder.INSTANCE; JavaBeanBinder javaBeanBinder = JavaBeanBinder.INSTANCE;
this.dataObjectBinders = Collections.unmodifiableList(Arrays.asList(valueObjectBinder, javaBeanBinder)); Map<BindMethod, List<DataObjectBinder>> dataObjectBinders = new HashMap<>();
dataObjectBinders.put(BindMethod.VALUE_OBJECT, List.of(valueObjectBinder));
dataObjectBinders.put(BindMethod.JAVA_BEAN, List.of(javaBeanBinder));
dataObjectBinders.put(null, List.of(valueObjectBinder, javaBeanBinder));
this.dataObjectBinders = Collections.unmodifiableMap(dataObjectBinders);
} }
/** /**
@ -365,7 +370,7 @@ public class Binder {
} }
private Object create(Bindable<?> target, Context context) { private Object create(Bindable<?> target, Context context) {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) { for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(target.getBindMethod())) {
Object instance = dataObjectBinder.create(target, context); Object instance = dataObjectBinder.create(target, context);
if (instance != null) { if (instance != null) {
return instance; return instance;
@ -466,13 +471,14 @@ public class Binder {
return null; return null;
} }
Class<?> type = target.getType().resolve(Object.class); Class<?> type = target.getType().resolve(Object.class);
BindMethod bindMethod = target.getBindMethod();
if (!allowRecursiveBinding && context.isBindingDataObject(type)) { if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
return null; return null;
} }
DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName), DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false, false); propertyTarget, handler, context, false, false);
return context.withDataObject(type, () -> { return context.withDataObject(type, () -> {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) { for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(bindMethod)) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder); Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) { if (instance != null) {
return instance; return instance;

@ -25,7 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.function.ThrowingConsumer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.BindConstructorProvider;
import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -64,19 +64,19 @@ class ConfigurationPropertiesBeanTests {
assertThat(component.getInstance()).isInstanceOf(AnnotatedComponent.class); assertThat(component.getInstance()).isInstanceOf(AnnotatedComponent.class);
assertThat(component.getAnnotation()).isNotNull(); assertThat(component.getAnnotation()).isNotNull();
assertThat(component.getType()).isEqualTo(AnnotatedComponent.class); assertThat(component.getType()).isEqualTo(AnnotatedComponent.class);
assertThat(component.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(component.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
ConfigurationPropertiesBean bean = all.get("annotatedBean"); ConfigurationPropertiesBean bean = all.get("annotatedBean");
assertThat(bean.getName()).isEqualTo("annotatedBean"); assertThat(bean.getName()).isEqualTo("annotatedBean");
assertThat(bean.getInstance()).isInstanceOf(AnnotatedBean.class); assertThat(bean.getInstance()).isInstanceOf(AnnotatedBean.class);
assertThat(bean.getType()).isEqualTo(AnnotatedBean.class); assertThat(bean.getType()).isEqualTo(AnnotatedBean.class);
assertThat(bean.getAnnotation()).isNotNull(); assertThat(bean.getAnnotation()).isNotNull();
assertThat(bean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
ConfigurationPropertiesBean valueObject = all.get(ValueObject.class.getName()); ConfigurationPropertiesBean valueObject = all.get(ValueObject.class.getName());
assertThat(valueObject.getName()).isEqualTo(ValueObject.class.getName()); assertThat(valueObject.getName()).isEqualTo(ValueObject.class.getName());
assertThat(valueObject.getInstance()).isInstanceOf(ValueObject.class); assertThat(valueObject.getInstance()).isInstanceOf(ValueObject.class);
assertThat(valueObject.getType()).isEqualTo(ValueObject.class); assertThat(valueObject.getType()).isEqualTo(ValueObject.class);
assertThat(valueObject.getAnnotation()).isNotNull(); assertThat(valueObject.getAnnotation()).isNotNull();
assertThat(valueObject.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(valueObject.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
} }
} }
@ -113,7 +113,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getInstance()).isInstanceOf(AnnotatedComponent.class); assertThat(propertiesBean.getInstance()).isInstanceOf(AnnotatedComponent.class);
assertThat(propertiesBean.getType()).isEqualTo(AnnotatedComponent.class); assertThat(propertiesBean.getType()).isEqualTo(AnnotatedComponent.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix"); assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
}); });
} }
@ -125,7 +125,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class); assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class);
assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class); assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix"); assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
}); });
} }
@ -138,7 +138,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class); assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class);
assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class); assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix"); assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
}); });
} }
@ -150,7 +150,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class); assertThat(propertiesBean.getInstance()).isInstanceOf(NonAnnotatedBean.class);
assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class); assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedBean.class);
assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix"); assertThat(propertiesBean.getAnnotation().prefix()).isEqualTo("prefix");
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
}); });
} }
@ -158,7 +158,7 @@ class ConfigurationPropertiesBeanTests {
void getWhenHasFactoryMethodBindsUsingMethodReturnType() throws Throwable { void getWhenHasFactoryMethodBindsUsingMethodReturnType() throws Throwable {
get(NonAnnotatedGenericBeanConfiguration.class, "nonAnnotatedGenericBean", (propertiesBean) -> { get(NonAnnotatedGenericBeanConfiguration.class, "nonAnnotatedGenericBean", (propertiesBean) -> {
assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedGenericBean.class); assertThat(propertiesBean.getType()).isEqualTo(NonAnnotatedGenericBean.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
ResolvableType type = propertiesBean.asBindTarget().getType(); ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(NonAnnotatedGenericBean.class); assertThat(type.resolve()).isEqualTo(NonAnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class); assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
@ -169,7 +169,7 @@ class ConfigurationPropertiesBeanTests {
void getWhenHasFactoryMethodWithoutAnnotationBindsUsingMethodType() throws Throwable { void getWhenHasFactoryMethodWithoutAnnotationBindsUsingMethodType() throws Throwable {
get(AnnotatedGenericBeanConfiguration.class, "annotatedGenericBean", (propertiesBean) -> { get(AnnotatedGenericBeanConfiguration.class, "annotatedGenericBean", (propertiesBean) -> {
assertThat(propertiesBean.getType()).isEqualTo(AnnotatedGenericBean.class); assertThat(propertiesBean.getType()).isEqualTo(AnnotatedGenericBean.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
ResolvableType type = propertiesBean.asBindTarget().getType(); ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericBean.class); assertThat(type.resolve()).isEqualTo(AnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class); assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
@ -180,7 +180,7 @@ class ConfigurationPropertiesBeanTests {
void getWhenHasNoFactoryMethodBindsUsingObjectType() throws Throwable { void getWhenHasNoFactoryMethodBindsUsingObjectType() throws Throwable {
get(AnnotatedGenericComponent.class, "annotatedGenericComponent", (propertiesBean) -> { get(AnnotatedGenericComponent.class, "annotatedGenericComponent", (propertiesBean) -> {
assertThat(propertiesBean.getType()).isEqualTo(AnnotatedGenericComponent.class); assertThat(propertiesBean.getType()).isEqualTo(AnnotatedGenericComponent.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.JAVA_BEAN);
ResolvableType type = propertiesBean.asBindTarget().getType(); ResolvableType type = propertiesBean.asBindTarget().getType();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericComponent.class); assertThat(type.resolve()).isEqualTo(AnnotatedGenericComponent.class);
assertThat(type.getGeneric(0).resolve()).isNull(); assertThat(type.getGeneric(0).resolve()).isNull();
@ -224,7 +224,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean"); assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull(); assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(ConstructorBindingOnConstructor.class); assertThat(propertiesBean.getType()).isEqualTo(ConstructorBindingOnConstructor.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull(); assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget(); Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class)); assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
@ -234,14 +234,13 @@ class ConfigurationPropertiesBeanTests {
} }
@Test @Test
@Deprecated(since = "3.0.0", forRemoval = true)
void forValueObjectWithDeprecatedConstructorBindingAnnotatedClassReturnsBean() { void forValueObjectWithDeprecatedConstructorBindingAnnotatedClassReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
.forValueObject(DeprecatedConstructorBindingOnConstructor.class, "valueObjectBean"); .forValueObject(DeprecatedConstructorBindingOnConstructor.class, "valueObjectBean");
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean"); assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull(); assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(DeprecatedConstructorBindingOnConstructor.class); assertThat(propertiesBean.getType()).isEqualTo(DeprecatedConstructorBindingOnConstructor.class);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull(); assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget(); Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()) assertThat(target.getType())
@ -269,7 +268,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getName()).isEqualTo("implicitBindingRecord"); assertThat(propertiesBean.getName()).isEqualTo("implicitBindingRecord");
assertThat(propertiesBean.getInstance()).isNull(); assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(implicitConstructorBinding); assertThat(propertiesBean.getType()).isEqualTo(implicitConstructorBinding);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull(); assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget(); Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(implicitConstructorBinding)); assertThat(target.getType()).isEqualTo(ResolvableType.forClass(implicitConstructorBinding));
@ -292,64 +291,66 @@ class ConfigurationPropertiesBeanTests {
} }
@Test @Test
void bindMethodGetWhenNoConstructorBindingReturnsJavaBean() { void deduceBindMethodWhenNoConstructorBindingReturnsJavaBean() {
BindMethod bindType = BindMethod.get(NoConstructorBinding.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(NoConstructorBinding.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }
@Test @Test
void bindMethodGetWhenConstructorBindingOnConstructorReturnsValueObject() { void deduceBindMethodWhenConstructorBindingOnConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.get(ConstructorBindingOnConstructor.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingOnConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
} }
@Test @Test
void bindMethodGetWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() { void deduceBindMethodWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.get(ConstructorBindingNoAnnotation.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingNoAnnotation.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT); assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
} }
@Test @Test
void bindMethodGetWhenConstructorBindingOnMultipleConstructorsThrowsException() { void deduceBindMethodWhenConstructorBindingOnMultipleConstructorsThrowsException() {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> BindMethod.get(ConstructorBindingOnMultipleConstructors.class)) .isThrownBy(
() -> ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingOnMultipleConstructors.class))
.withMessage(ConstructorBindingOnMultipleConstructors.class.getName() .withMessage(ConstructorBindingOnMultipleConstructors.class.getName()
+ " has more than one @ConstructorBinding constructor"); + " has more than one @ConstructorBinding constructor");
} }
@Test @Test
void bindMethodGetWithMultipleConstructorsReturnJavaBean() { void deduceBindMethodWithMultipleConstructorsReturnJavaBean() {
BindMethod bindType = BindMethod.get(NoConstructorBindingOnMultipleConstructors.class); BindMethod bindType = ConfigurationPropertiesBean
.deduceBindMethod(NoConstructorBindingOnMultipleConstructors.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }
@Test @Test
void bindMethodGetWithNoArgConstructorReturnsJavaBean() { void deduceBindMethodWithNoArgConstructorReturnsJavaBean() {
BindMethod bindType = BindMethod.get(JavaBeanWithNoArgConstructor.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(JavaBeanWithNoArgConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }
@Test @Test
void bindMethodGetWithSingleArgAutowiredConstructorReturnsJavaBean() { void deduceBindMethodWithSingleArgAutowiredConstructorReturnsJavaBean() {
BindMethod bindType = BindMethod.get(JavaBeanWithAutowiredConstructor.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(JavaBeanWithAutowiredConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }
@Test @Test
void constructorBindingAndAutowiredConstructorsShouldThrowException() { void constructorBindingAndAutowiredConstructorsShouldThrowException() {
assertThatIllegalStateException() assertThatIllegalStateException().isThrownBy(
.isThrownBy(() -> BindMethod.get(ConstructorBindingAndAutowiredConstructors.class)); () -> ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingAndAutowiredConstructors.class));
} }
@Test @Test
void innerClassWithSyntheticFieldShouldReturnJavaBean() { void innerClassWithSyntheticFieldShouldReturnJavaBean() {
BindMethod bindType = BindMethod.get(Inner.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(Inner.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }
@Test @Test
void innerClassWithParameterizedConstructorShouldReturnJavaBean() { void innerClassWithParameterizedConstructorShouldReturnJavaBean() {
BindMethod bindType = BindMethod.get(ParameterizedConstructorInner.class); BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ParameterizedConstructorInner.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
} }

@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.boot.context.properties.scan.combined.c.CombinedConfiguration; import org.springframework.boot.context.properties.scan.combined.c.CombinedConfiguration;
import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration; import org.springframework.boot.context.properties.scan.combined.d.OtherCombinedConfiguration;
import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration; import org.springframework.boot.context.properties.scan.valid.ConfigurationPropertiesScanConfiguration;

@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod; import org.springframework.boot.context.properties.bind.BindMethod;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;

@ -29,6 +29,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -190,9 +191,64 @@ class BindableTests {
assertThat(restricted.hasBindRestriction(BindRestriction.NO_DIRECT_PROPERTY)).isTrue(); assertThat(restricted.hasBindRestriction(BindRestriction.NO_DIRECT_PROPERTY)).isTrue();
} }
@Test
void whenTypeCouldUseJavaBeanOrValueObjectJavaBeanBindingCanBeSpecified() {
BindMethod bindMethod = Bindable.of(JavaBeanOrValueObject.class)
.withBindMethod(BindMethod.JAVA_BEAN)
.getBindMethod();
assertThat(bindMethod).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void whenTypeCouldUseJavaBeanOrValueObjectExistingValueForcesJavaBeanBinding() {
BindMethod bindMethod = Bindable.of(JavaBeanOrValueObject.class)
.withExistingValue(new JavaBeanOrValueObject("value"))
.getBindMethod();
assertThat(bindMethod).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void whenBindingIsValueObjectExistingValueThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> Bindable.of(JavaBeanOrValueObject.class)
.withBindMethod(BindMethod.VALUE_OBJECT)
.withExistingValue(new JavaBeanOrValueObject("value")));
}
@Test
void whenBindableHasExistingValueValueObjectBindMethodThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> Bindable.of(JavaBeanOrValueObject.class)
.withExistingValue(new JavaBeanOrValueObject("value"))
.withBindMethod(BindMethod.VALUE_OBJECT));
}
@Test
void whenBindableHasSuppliedValueValueObjectBindMethodThrowsException() {
assertThatIllegalStateException().isThrownBy(() -> Bindable.of(JavaBeanOrValueObject.class)
.withSuppliedValue(() -> new JavaBeanOrValueObject("value"))
.withBindMethod(BindMethod.VALUE_OBJECT));
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation { @interface TestAnnotation {
} }
static class JavaBeanOrValueObject {
private String property;
JavaBeanOrValueObject(String property) {
this.property = property;
}
String getProperty() {
return this.property;
}
void setProperty(String property) {
this.property = property;
}
}
} }

@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.beans.factory.support.DefaultListableBeanFactory import org.springframework.beans.factory.support.DefaultListableBeanFactory
import org.springframework.beans.factory.support.RootBeanDefinition import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.boot.context.properties.bind.BindMethod
/** /**
* Tests for `ConfigurationPropertiesBeanRegistrar`. * Tests for `ConfigurationPropertiesBeanRegistrar`.
@ -30,9 +31,8 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
this.registrar.register(BarProperties::class.java) this.registrar.register(BarProperties::class.java)
val beanDefinition = this.beanFactory.getBeanDefinition( val beanDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties") "bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties")
assertThat(beanDefinition.hasAttribute(ConfigurationPropertiesBean.BindMethod::class.java.name)).isTrue() assertThat(beanDefinition.hasAttribute(BindMethod::class.java.name)).isTrue()
assertThat(beanDefinition.getAttribute(ConfigurationPropertiesBean.BindMethod::class.java.name)) assertThat(beanDefinition.getAttribute(BindMethod::class.java.name)).isEqualTo(BindMethod.VALUE_OBJECT)
.isEqualTo(ConfigurationPropertiesBean.BindMethod.VALUE_OBJECT)
} }
@Test @Test

Loading…
Cancel
Save