From f03f062770c424f1b97a23739792a5161a92846f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 19 May 2023 19:09:47 +0100 Subject: [PATCH] Move BindMethod to context.properties.bind and expose on Bindable Closes gh-35642 Co-authored-by: Phillip Webb --- .../properties/BindMethodAttribute.java | 2 +- .../ConfigurationPropertiesBean.java | 86 +++++++++++++------ .../ConfigurationPropertiesBeanRegistrar.java | 4 +- ...ropertiesBeanRegistrationAotProcessor.java | 2 +- ...urationPropertiesBindingPostProcessor.java | 7 +- ...structorBoundInjectionFailureAnalyzer.java | 21 ++--- .../context/properties/bind/BindMethod.java | 37 ++++++++ .../context/properties/bind/Bindable.java | 51 +++++++++-- .../boot/context/properties/bind/Binder.java | 14 ++- ...igurationPropertiesBeanRegistrarTests.java | 2 +- .../ConfigurationPropertiesBeanTests.java | 67 ++++++++------- ...igurationPropertiesScanRegistrarTests.java | 2 +- ...ConfigurationPropertiesRegistrarTests.java | 2 +- .../properties/bind/BindableTests.java | 56 ++++++++++++ ...nfigurationPropertiesBeanRegistrarTests.kt | 6 +- 15 files changed, 265 insertions(+), 94 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindMethod.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java index 06dfdc75bb..ac4fc5a816 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java @@ -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; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java index f1f9c74cda..3e42c133fb 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java @@ -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 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 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 bindTarget, - BindMethod bindMethod) { - return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget, bindMethod) : null; + private static ConfigurationPropertiesBean create(String name, Object instance, Bindable 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 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; + }; } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java index eead4a5a75..8bf31a0bae 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrar.java @@ -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) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java index 7f4890a937..623626154b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java @@ -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; /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index a0f5772de9..c80c0526e5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -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); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java index 5769f61c6e..2e4fc05a48 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java @@ -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 = 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) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindMethod.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindMethod.java new file mode 100644 index 0000000000..7039ca43ec --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindMethod.java @@ -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; + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java index 67bff4f621..c5a7dfcd1a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java @@ -57,13 +57,16 @@ public final class Bindable { private final EnumSet bindRestrictions; + private final BindMethod bindMethod; + private Bindable(ResolvableType type, ResolvableType boxedType, Supplier value, Annotation[] annotations, - EnumSet bindRestrictions) { + EnumSet 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 { 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 { 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 { 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 { 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 { */ public Bindable 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 { 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 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 { * @return an updated {@link Bindable} */ public Bindable withSuppliedValue(Supplier 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 { public Bindable withBindRestrictions(BindRestriction... additionalRestrictions) { EnumSet 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 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 { public static Bindable 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) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index ffbf3df7b4..c092448465 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -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 dataObjectBinders; + private final Map> 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> 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; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java index e0bf3eb7f6..66989ada16 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrarTests.java @@ -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; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java index 9eaffa3d6a..f48d5be952 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanTests.java @@ -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); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java index c35deaacb0..66f3148f10 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrarTests.java @@ -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; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrarTests.java index ed45f33d3d..7fd7bb7a5a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrarTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrarTests.java @@ -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; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableTests.java index 5409a2cbf3..217f41ac22 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableTests.java @@ -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; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesBeanRegistrarTests.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesBeanRegistrarTests.kt index b51333a88f..cc50d38eda 100644 --- a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesBeanRegistrarTests.kt +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesBeanRegistrarTests.kt @@ -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