Move BindMethod to context.properties.bind and expose on Bindable

Closes gh-35642

Co-authored-by: Phillip Webb <pwebb@vmware.com>
pull/35611/head
Andy Wilkinson 2 years ago
parent 581a32b107
commit f03f062770

@ -18,7 +18,7 @@ package org.springframework.boot.context.properties;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
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.ConfigurableApplicationContext;
import org.springframework.core.AttributeAccessor;

@ -61,19 +61,22 @@ import org.springframework.validation.annotation.Validated;
*/
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 Object instance;
private final Bindable<?> bindTarget;
private final BindMethod bindMethod;
private ConfigurationPropertiesBean(String name, Object instance, Bindable<?> bindTarget, BindMethod bindMethod) {
private ConfigurationPropertiesBean(String name, Object instance, Bindable<?> bindTarget) {
this.name = name;
this.instance = instance;
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 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() {
return this.bindMethod;
return BindMethod.from(this.bindTarget.getBindMethod());
}
/**
@ -206,14 +212,17 @@ public final class ConfigurationPropertiesBean {
if (bindTarget == null) {
return null;
}
BindMethod bindMethod = BindMethodAttribute.get(applicationContext, beanName);
if (bindMethod == null && factoryMethod != null) {
bindMethod = BindMethod.JAVA_BEAN;
bindTarget = bindTarget.withBindMethod(BindMethodAttribute.get(applicationContext, beanName));
if (bindTarget.getBindMethod() == null && factoryMethod != null) {
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);
}
return create(beanName, bean, bindTarget, bindMethod);
return create(beanName, bean, bindTarget);
}
private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) {
@ -263,10 +272,9 @@ public final class ConfigurationPropertiesBean {
static ConfigurationPropertiesBean forValueObject(Class<?> beanType, String beanName) {
Bindable<Object> bindTarget = createBindTarget(null, beanType, null);
ConfigurationPropertiesBean propertiesBean = create(beanName, null, bindTarget, null);
Assert.state(propertiesBean != null && propertiesBean.getBindMethod() == BindMethod.VALUE_OBJECT,
Assert.state(bindTarget != null && deduceBindMethod(bindTarget) == VALUE_OBJECT_BIND_METHOD,
() -> "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) {
@ -307,14 +315,40 @@ public final class ConfigurationPropertiesBean {
: MergedAnnotation.missing();
}
private static ConfigurationPropertiesBean create(String name, Object instance, Bindable<Object> bindTarget,
BindMethod bindMethod) {
return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget, bindMethod) : null;
private static ConfigurationPropertiesBean create(String name, Object instance, Bindable<Object> bindTarget) {
return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget) : 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.
*
* @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 {
/**
@ -327,16 +361,14 @@ public final class ConfigurationPropertiesBean {
*/
VALUE_OBJECT;
static BindMethod get(Class<?> type) {
return get(BindConstructorProvider.DEFAULT.getBindConstructor(type, false));
}
static BindMethod get(Bindable<?> bindable) {
return get(BindConstructorProvider.DEFAULT.getBindConstructor(bindable, false));
}
private static BindMethod get(Constructor<?> bindConstructor) {
return (bindConstructor != null) ? VALUE_OBJECT : JAVA_BEAN;
static BindMethod from(org.springframework.boot.context.properties.bind.BindMethod bindMethod) {
if (bindMethod == null) {
return null;
}
return switch (bindMethod) {
case VALUE_OBJECT -> BindMethod.VALUE_OBJECT;
case JAVA_BEAN -> BindMethod.JAVA_BEAN;
};
}
}

@ -22,7 +22,7 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
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.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
@ -90,7 +90,7 @@ final class ConfigurationPropertiesBeanRegistrar {
}
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
BindMethod bindMethod = BindMethod.get(type);
BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(type);
RootBeanDefinition definition = new RootBeanDefinition(type);
BindMethodAttribute.set(definition, bindMethod);
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.RegisteredBean;
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;
/**

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

@ -21,13 +21,13 @@ import java.lang.reflect.Constructor;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAnalyzer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
/**
* An {@link AbstractInjectionFailureAnalyzer} for
@ -61,14 +61,15 @@ class NotConstructorBoundInjectionFailureAnalyzer
}
private boolean isConstructorBindingConfigurationProperties(InjectionPoint injectionPoint) {
if (injectionPoint != null && injectionPoint.getMember() instanceof Constructor<?> constructor) {
Class<?> declaringClass = constructor.getDeclaringClass();
MergedAnnotation<ConfigurationProperties> configurationProperties = MergedAnnotations.from(declaringClass)
.get(ConfigurationProperties.class);
return configurationProperties.isPresent()
&& BindMethod.get(constructor.getDeclaringClass()) == BindMethod.VALUE_OBJECT;
}
return false;
return (injectionPoint != null && injectionPoint.getMember() instanceof Constructor<?> constructor)
? isConstructorBindingConfigurationProperties(constructor) : false;
}
private boolean isConstructorBindingConfigurationProperties(Constructor<?> constructor) {
Class<?> declaringClass = constructor.getDeclaringClass();
BindMethod bindMethod = ConfigurationPropertiesBean.deduceBindMethod(declaringClass);
return MergedAnnotations.from(declaringClass, SearchStrategy.TYPE_HIERARCHY)
.isPresent(ConfigurationProperties.class) && bindMethod == BindMethod.VALUE_OBJECT;
}
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 BindMethod bindMethod;
private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value, Annotation[] annotations,
EnumSet<BindRestriction> bindRestrictions) {
EnumSet<BindRestriction> bindRestrictions, BindMethod bindMethod) {
this.type = type;
this.boxedType = boxedType;
this.value = value;
this.annotations = annotations;
this.bindRestrictions = bindRestrictions;
this.bindMethod = bindMethod;
}
/**
@ -124,6 +127,16 @@ public final class Bindable<T> {
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
public boolean equals(Object 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.annotations, other.annotations);
result = result && nullSafeEquals(this.bindRestrictions, other.bindRestrictions);
result = result && nullSafeEquals(this.bindMethod, other.bindMethod);
return result;
}
@ -147,6 +161,7 @@ public final class Bindable<T> {
result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
result = prime * result + ObjectUtils.nullSafeHashCode(this.bindRestrictions);
result = prime * result + ObjectUtils.nullSafeHashCode(this.bindMethod);
return result;
}
@ -156,6 +171,7 @@ public final class Bindable<T> {
creator.append("type", this.type);
creator.append("value", (this.value != null) ? "provided" : "none");
creator.append("annotations", this.annotations);
creator.append("bindMethod", this.bindMethod);
return creator.toString();
}
@ -170,11 +186,12 @@ public final class Bindable<T> {
*/
public Bindable<T> withAnnotations(Annotation... annotations) {
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
* @return an updated {@link Bindable}
*/
@ -182,8 +199,11 @@ public final class Bindable<T> {
Assert.isTrue(
existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue),
() -> "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;
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}
*/
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) {
EnumSet<BindRestriction> bindRestrictions = EnumSet.copyOf(this.bindRestrictions);
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) {
Assert.notNull(type, "Type must not be null");
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) {

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -64,7 +65,7 @@ public class Binder {
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
@ -194,7 +195,11 @@ public class Binder {
}
ValueObjectBinder valueObjectBinder = new ValueObjectBinder(constructorProvider);
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) {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(target.getBindMethod())) {
Object instance = dataObjectBinder.create(target, context);
if (instance != null) {
return instance;
@ -466,13 +471,14 @@ public class Binder {
return null;
}
Class<?> type = target.getType().resolve(Object.class);
BindMethod bindMethod = target.getBindMethod();
if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
return null;
}
DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false, false);
return context.withDataObject(type, () -> {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders) {
for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(bindMethod)) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) {
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.GenericBeanDefinition;
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.assertThatIllegalStateException;

@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.ThrowingConsumer;
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.BindMethod;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -64,19 +64,19 @@ class ConfigurationPropertiesBeanTests {
assertThat(component.getInstance()).isInstanceOf(AnnotatedComponent.class);
assertThat(component.getAnnotation()).isNotNull();
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");
assertThat(bean.getName()).isEqualTo("annotatedBean");
assertThat(bean.getInstance()).isInstanceOf(AnnotatedBean.class);
assertThat(bean.getType()).isEqualTo(AnnotatedBean.class);
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());
assertThat(valueObject.getName()).isEqualTo(ValueObject.class.getName());
assertThat(valueObject.getInstance()).isInstanceOf(ValueObject.class);
assertThat(valueObject.getType()).isEqualTo(ValueObject.class);
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.getType()).isEqualTo(AnnotatedComponent.class);
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.getType()).isEqualTo(NonAnnotatedBean.class);
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.getType()).isEqualTo(NonAnnotatedBean.class);
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.getType()).isEqualTo(NonAnnotatedBean.class);
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 {
get(NonAnnotatedGenericBeanConfiguration.class, "nonAnnotatedGenericBean", (propertiesBean) -> {
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();
assertThat(type.resolve()).isEqualTo(NonAnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
@ -169,7 +169,7 @@ class ConfigurationPropertiesBeanTests {
void getWhenHasFactoryMethodWithoutAnnotationBindsUsingMethodType() throws Throwable {
get(AnnotatedGenericBeanConfiguration.class, "annotatedGenericBean", (propertiesBean) -> {
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();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericBean.class);
assertThat(type.resolveGeneric(0)).isEqualTo(String.class);
@ -180,7 +180,7 @@ class ConfigurationPropertiesBeanTests {
void getWhenHasNoFactoryMethodBindsUsingObjectType() throws Throwable {
get(AnnotatedGenericComponent.class, "annotatedGenericComponent", (propertiesBean) -> {
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();
assertThat(type.resolve()).isEqualTo(AnnotatedGenericComponent.class);
assertThat(type.getGeneric(0).resolve()).isNull();
@ -224,7 +224,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull();
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();
Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class));
@ -234,14 +234,13 @@ class ConfigurationPropertiesBeanTests {
}
@Test
@Deprecated(since = "3.0.0", forRemoval = true)
void forValueObjectWithDeprecatedConstructorBindingAnnotatedClassReturnsBean() {
ConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean
.forValueObject(DeprecatedConstructorBindingOnConstructor.class, "valueObjectBean");
assertThat(propertiesBean.getName()).isEqualTo("valueObjectBean");
assertThat(propertiesBean.getInstance()).isNull();
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();
Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType())
@ -269,7 +268,7 @@ class ConfigurationPropertiesBeanTests {
assertThat(propertiesBean.getName()).isEqualTo("implicitBindingRecord");
assertThat(propertiesBean.getInstance()).isNull();
assertThat(propertiesBean.getType()).isEqualTo(implicitConstructorBinding);
assertThat(propertiesBean.getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.asBindTarget().getBindMethod()).isEqualTo(BindMethod.VALUE_OBJECT);
assertThat(propertiesBean.getAnnotation()).isNotNull();
Bindable<?> target = propertiesBean.asBindTarget();
assertThat(target.getType()).isEqualTo(ResolvableType.forClass(implicitConstructorBinding));
@ -292,64 +291,66 @@ class ConfigurationPropertiesBeanTests {
}
@Test
void bindMethodGetWhenNoConstructorBindingReturnsJavaBean() {
BindMethod bindType = BindMethod.get(NoConstructorBinding.class);
void deduceBindMethodWhenNoConstructorBindingReturnsJavaBean() {
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(NoConstructorBinding.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void bindMethodGetWhenConstructorBindingOnConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.get(ConstructorBindingOnConstructor.class);
void deduceBindMethodWhenConstructorBindingOnConstructorReturnsValueObject() {
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingOnConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
}
@Test
void bindMethodGetWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() {
BindMethod bindType = BindMethod.get(ConstructorBindingNoAnnotation.class);
void deduceBindMethodWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() {
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingNoAnnotation.class);
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
}
@Test
void bindMethodGetWhenConstructorBindingOnMultipleConstructorsThrowsException() {
void deduceBindMethodWhenConstructorBindingOnMultipleConstructorsThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> BindMethod.get(ConstructorBindingOnMultipleConstructors.class))
.isThrownBy(
() -> ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingOnMultipleConstructors.class))
.withMessage(ConstructorBindingOnMultipleConstructors.class.getName()
+ " has more than one @ConstructorBinding constructor");
}
@Test
void bindMethodGetWithMultipleConstructorsReturnJavaBean() {
BindMethod bindType = BindMethod.get(NoConstructorBindingOnMultipleConstructors.class);
void deduceBindMethodWithMultipleConstructorsReturnJavaBean() {
BindMethod bindType = ConfigurationPropertiesBean
.deduceBindMethod(NoConstructorBindingOnMultipleConstructors.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void bindMethodGetWithNoArgConstructorReturnsJavaBean() {
BindMethod bindType = BindMethod.get(JavaBeanWithNoArgConstructor.class);
void deduceBindMethodWithNoArgConstructorReturnsJavaBean() {
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(JavaBeanWithNoArgConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void bindMethodGetWithSingleArgAutowiredConstructorReturnsJavaBean() {
BindMethod bindType = BindMethod.get(JavaBeanWithAutowiredConstructor.class);
void deduceBindMethodWithSingleArgAutowiredConstructorReturnsJavaBean() {
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(JavaBeanWithAutowiredConstructor.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void constructorBindingAndAutowiredConstructorsShouldThrowException() {
assertThatIllegalStateException()
.isThrownBy(() -> BindMethod.get(ConstructorBindingAndAutowiredConstructors.class));
assertThatIllegalStateException().isThrownBy(
() -> ConfigurationPropertiesBean.deduceBindMethod(ConstructorBindingAndAutowiredConstructors.class));
}
@Test
void innerClassWithSyntheticFieldShouldReturnJavaBean() {
BindMethod bindType = BindMethod.get(Inner.class);
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(Inner.class);
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
}
@Test
void innerClassWithParameterizedConstructorShouldReturnJavaBean() {
BindMethod bindType = BindMethod.get(ParameterizedConstructorInner.class);
BindMethod bindType = ConfigurationPropertiesBean.deduceBindMethod(ParameterizedConstructorInner.class);
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.support.DefaultListableBeanFactory;
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.d.OtherCombinedConfiguration;
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.support.DefaultListableBeanFactory;
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 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.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
/**
@ -190,9 +191,64 @@ class BindableTests {
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)
@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.springframework.beans.factory.support.DefaultListableBeanFactory
import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.boot.context.properties.bind.BindMethod
/**
* Tests for `ConfigurationPropertiesBeanRegistrar`.
@ -30,9 +31,8 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
this.registrar.register(BarProperties::class.java)
val beanDefinition = this.beanFactory.getBeanDefinition(
"bar-org.springframework.boot.context.properties.KotlinConfigurationPropertiesBeanRegistrarTests\$BarProperties")
assertThat(beanDefinition.hasAttribute(ConfigurationPropertiesBean.BindMethod::class.java.name)).isTrue()
assertThat(beanDefinition.getAttribute(ConfigurationPropertiesBean.BindMethod::class.java.name))
.isEqualTo(ConfigurationPropertiesBean.BindMethod.VALUE_OBJECT)
assertThat(beanDefinition.hasAttribute(BindMethod::class.java.name)).isTrue()
assertThat(beanDefinition.getAttribute(BindMethod::class.java.name)).isEqualTo(BindMethod.VALUE_OBJECT)
}
@Test

Loading…
Cancel
Save