diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java index 4756391471a..cb1da1cf7ee 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java @@ -62,7 +62,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.context.properties.BoundConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesBean; -import org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider; +import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Name; import org.springframework.boot.context.properties.source.ConfigurationProperty; @@ -476,8 +476,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext List result = new ArrayList<>(); Class beanClass = beanDesc.getType().getRawClass(); Bindable bindable = Bindable.of(ClassUtils.getUserClass(beanClass)); - Constructor bindConstructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE - .getBindConstructor(bindable, false); + Constructor bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(bindable, false); for (BeanPropertyWriter writer : beanProperties) { if (isCandidate(beanDesc, writer, bindConstructor)) { result.add(writer); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java index 8309dd21598..dc7b36559c7 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java @@ -34,8 +34,8 @@ import org.springframework.boot.actuate.context.properties.ConfigurationProperti import org.springframework.boot.actuate.endpoint.SanitizingFunction; import org.springframework.boot.actuate.endpoint.Show; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.ConstructorBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.Name; import org.springframework.boot.origin.Origin; 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 009445a3310..1d1ae6a7bfc 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 @@ -18,6 +18,7 @@ package org.springframework.boot.context.properties; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Iterator; import java.util.LinkedHashMap; @@ -30,6 +31,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.ApplicationContext; @@ -70,8 +72,8 @@ public final class ConfigurationPropertiesBean { private final BindMethod bindMethod; private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation, - Bindable bindTarget) { - this(name, instance, annotation, bindTarget, BindMethod.forType(bindTarget.getType().resolve())); + Bindable bindable) { + this(name, instance, annotation, bindable, BindMethod.get(bindable)); } private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation, @@ -274,14 +276,14 @@ public final class ConfigurationPropertiesBean { : new Annotation[] { annotation }; ResolvableType bindType = (factory != null) ? ResolvableType.forMethodReturnType(factory) : ResolvableType.forClass(type); - Bindable bindTarget = Bindable.of(bindType).withAnnotations(annotations); + Bindable bindable = Bindable.of(bindType).withAnnotations(annotations); if (instance != null) { - bindTarget = bindTarget.withExistingValue(instance); + bindable = bindable.withExistingValue(instance); } if (factory != null) { - return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget, BindMethod.JAVA_BEAN); + return new ConfigurationPropertiesBean(name, instance, annotation, bindable, BindMethod.JAVA_BEAN); } - return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget); + return new ConfigurationPropertiesBean(name, instance, annotation, bindable); } private static A findAnnotation(Object instance, Class type, Method factory, @@ -321,9 +323,16 @@ public final class ConfigurationPropertiesBean { */ VALUE_OBJECT; - static BindMethod forType(Class type) { - return (ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(type, false) != null) - ? VALUE_OBJECT : JAVA_BEAN; + 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; } } 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 704eef54ac3..2988233dc26 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 @@ -89,7 +89,7 @@ final class ConfigurationPropertiesBeanRegistrar { } private BeanDefinition createBeanDefinition(String beanName, Class type) { - BindMethod bindMethod = BindMethod.forType(type); + BindMethod bindMethod = BindMethod.get(type); RootBeanDefinition definition = new RootBeanDefinition(type); definition.setAttribute(BindMethod.class.getName(), bindMethod); if (bindMethod == BindMethod.VALUE_OBJECT) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java deleted file mode 100644 index 4d1dab6673c..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProvider.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2012-2022 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; - -import java.lang.reflect.Constructor; -import java.util.Arrays; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.bind.BindConstructorProvider; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.core.KotlinDetector; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.util.Assert; - -/** - * {@link BindConstructorProvider} used when binding - * {@link ConfigurationProperties @ConfigurationProperties}. - * - * @author Madhura Bhave - * @author Phillip Webb - * @since 3.0.0 - */ -public class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider { - - /** - * A shared singleton {@link ConfigurationPropertiesBindConstructorProvider} instance. - */ - public static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider(); - - @Override - public Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding) { - return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding); - } - - Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { - if (type == null) { - return null; - } - Constructors constructors = Constructors.getConstructors(type); - if (constructors.getBind() != null || isNestedConstructorBinding) { - Assert.state(!constructors.hasAutowired(), - () -> type.getName() + " declares @ConstructorBinding and @Autowired constructor"); - } - return constructors.getBind(); - } - - /** - * Data holder for autowired and bind constructors. - */ - static final class Constructors { - - private final boolean hasAutowired; - - private final Constructor bind; - - private Constructors(boolean hasAutowired, Constructor bind) { - this.hasAutowired = hasAutowired; - this.bind = bind; - } - - boolean hasAutowired() { - return this.hasAutowired; - } - - Constructor getBind() { - return this.bind; - } - - static Constructors getConstructors(Class type) { - Constructor[] candidates = getCandidateConstructors(type); - Constructor deducedBind = deduceBindConstructor(candidates); - if (deducedBind != null) { - return new Constructors(false, deducedBind); - } - boolean hasAutowiredConstructor = false; - Constructor bind = null; - for (Constructor candidate : candidates) { - if (isAutowired(candidate)) { - hasAutowiredConstructor = true; - continue; - } - bind = findAnnotatedConstructor(type, bind, candidate); - } - if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) { - bind = deduceKotlinBindConstructor(type); - } - return new Constructors(hasAutowiredConstructor, bind); - } - - private static Constructor[] getCandidateConstructors(Class type) { - if (isInnerClass(type)) { - return new Constructor[0]; - } - return Arrays.stream(type.getDeclaredConstructors()) - .filter((constructor) -> isNonSynthetic(constructor, type)).toArray(Constructor[]::new); - } - - private static boolean isInnerClass(Class type) { - try { - return type.getDeclaredField("this$0").isSynthetic(); - } - catch (NoSuchFieldException ex) { - return false; - } - } - - private static boolean isNonSynthetic(Constructor constructor, Class type) { - return !constructor.isSynthetic(); - } - - private static Constructor deduceBindConstructor(Constructor[] constructors) { - if (constructors.length == 1 && constructors[0].getParameterCount() > 0 && !isAutowired(constructors[0])) { - return constructors[0]; - } - return null; - } - - private static boolean isAutowired(Constructor candidate) { - return MergedAnnotations.from(candidate).isPresent(Autowired.class); - } - - private static Constructor findAnnotatedConstructor(Class type, Constructor constructor, - Constructor candidate) { - if (MergedAnnotations.from(candidate).isPresent(ConstructorBinding.class)) { - Assert.state(candidate.getParameterCount() > 0, - () -> type.getName() + " declares @ConstructorBinding on a no-args constructor"); - Assert.state(constructor == null, - () -> type.getName() + " has more than one @ConstructorBinding constructor"); - constructor = candidate; - } - return constructor; - } - - private static boolean isKotlinType(Class type) { - return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); - } - - private static Constructor deduceKotlinBindConstructor(Class type) { - Constructor primaryConstructor = BeanUtils.findPrimaryConstructor(type); - if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { - return primaryConstructor; - } - return null; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index f046e19b18a..5fe6661c04b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -164,8 +164,7 @@ class ConfigurationPropertiesBinder { private Binder getBinder() { if (this.binder == null) { this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(), - getConversionServices(), getPropertyEditorInitializer(), null, - ConfigurationPropertiesBindConstructorProvider.INSTANCE); + getConversionServices(), getPropertyEditorInitializer(), null, null); } return this.binder; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java index 801350984a4..deaed1726ff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java @@ -33,6 +33,7 @@ import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; import org.springframework.beans.BeanInfoFactory; import org.springframework.beans.ExtendedBeanInfoFactory; +import org.springframework.boot.context.properties.bind.BindConstructorProvider; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.MergedAnnotations; @@ -88,7 +89,7 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { private static Constructor getBindConstructor(Class type, boolean nestedType) { Bindable bindable = Bindable.of(type); - return ConfigurationPropertiesBindConstructorProvider.INSTANCE.getBindConstructor(bindable, nestedType); + return BindConstructorProvider.DEFAULT.getBindConstructor(bindable, nestedType); } private void process(ReflectionHints reflectionHints) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java index d40ee307f81..49c95249ee9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConstructorBinding.java @@ -39,10 +39,14 @@ import java.lang.annotation.Target; * @author Phillip Webb * @since 2.2.0 * @see ConfigurationProperties + * @deprecated since 3.0.0 for removal in 3.2.0 in favor of + * {@link org.springframework.boot.context.properties.bind.ConstructorBinding} */ -@Target(ElementType.CONSTRUCTOR) +@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented +@Deprecated(since = "3.0.0", forRemoval = true) +@org.springframework.boot.context.properties.bind.ConstructorBinding public @interface ConstructorBinding { } 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 b8166e8ae8f..b4b83fa858d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -22,6 +22,7 @@ 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.ConstructorBinding; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAnalyzer; import org.springframework.core.Ordered; @@ -66,7 +67,7 @@ class NotConstructorBoundInjectionFailureAnalyzer MergedAnnotation configurationProperties = MergedAnnotations.from(declaringClass) .get(ConfigurationProperties.class); return configurationProperties.isPresent() - && BindMethod.forType(constructor.getDeclaringClass()) == BindMethod.VALUE_OBJECT; + && BindMethod.get(constructor.getDeclaringClass()) == BindMethod.VALUE_OBJECT; } return false; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java index 84463231501..d9b39e18a6f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -33,6 +33,19 @@ public interface BindConstructorProvider { */ BindConstructorProvider DEFAULT = new DefaultBindConstructorProvider(); + /** + * Return the bind constructor to use for the given type, or {@code null} if + * constructor binding is not supported. + * @param type the type to check + * @param isNestedConstructorBinding if this binding is nested within a constructor + * binding + * @return the bind constructor or {@code null} + * @since 3.0.0 + */ + default Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { + return getBindConstructor(Bindable.of(type), isNestedConstructorBinding); + } + /** * Return the bind constructor to use for the given bindable, or {@code null} if * constructor binding is not supported. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ConstructorBinding.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ConstructorBinding.java new file mode 100644 index 00000000000..7bb82a2ec78 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ConstructorBinding.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2022 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be used to indicate which constructor to use when binding + * configuration properties using constructor arguments rather than by calling setters. A + * single parameterized constructor implicitly indicates that constructor binding should + * be used unless the constructor is annotated with `@Autowired`. + * + * @author Phillip Webb + * @since 3.0.0 + */ +@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConstructorBinding { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java index 91fd71dc39c..6d65256d284 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -18,9 +18,13 @@ package org.springframework.boot.context.properties.bind; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; +import java.util.Arrays; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.KotlinDetector; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.util.Assert; /** * Default {@link BindConstructorProvider} implementation. @@ -32,38 +36,140 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { @Override public Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding) { - Class type = bindable.getType().resolve(); - if (bindable.getValue() != null || type == null) { + return getBindConstructor(bindable.getType().resolve(), isNestedConstructorBinding); + } + + @Override + public Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { + if (type == null) { return null; } - if (KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type)) { - return getDeducedKotlinConstructor(type); + Constructors constructors = Constructors.getConstructors(type); + if (constructors.getBind() != null || isNestedConstructorBinding) { + Assert.state(!constructors.hasAutowired(), + () -> type.getName() + " declares @ConstructorBinding and @Autowired constructor"); + } + return constructors.getBind(); + } + + /** + * Data holder for autowired and bind constructors. + */ + static final class Constructors { + + private final boolean hasAutowired; + + private final Constructor bind; + + private Constructors(boolean hasAutowired, Constructor bind) { + this.hasAutowired = hasAutowired; + this.bind = bind; + } + + boolean hasAutowired() { + return this.hasAutowired; + } + + Constructor getBind() { + return this.bind; + } + + static Constructors getConstructors(Class type) { + Constructor[] candidates = getCandidateConstructors(type); + MergedAnnotations[] candidateAnnotations = getAnnotations(candidates); + boolean hasAutowiredConstructor = isAutowiredPresent(candidateAnnotations); + Constructor bind = getConstructorBindingAnnotated(type, candidates, candidateAnnotations); + if (bind == null && !hasAutowiredConstructor) { + bind = deduceBindConstructor(candidates); + } + if (bind == null && !hasAutowiredConstructor && isKotlinType(type)) { + bind = deduceKotlinBindConstructor(type); + } + return new Constructors(hasAutowiredConstructor, bind); + } + + private static Constructor[] getCandidateConstructors(Class type) { + if (isInnerClass(type)) { + return new Constructor[0]; + } + return Arrays.stream(type.getDeclaredConstructors()) + .filter((constructor) -> isNonSynthetic(constructor, type)).toArray(Constructor[]::new); + } + + private static boolean isInnerClass(Class type) { + try { + return type.getDeclaredField("this$0").isSynthetic(); + } + catch (NoSuchFieldException ex) { + return false; + } + } + + private static boolean isNonSynthetic(Constructor constructor, Class type) { + return !constructor.isSynthetic(); + } + + private static MergedAnnotations[] getAnnotations(Constructor[] candidates) { + MergedAnnotations[] candidateAnnotations = new MergedAnnotations[candidates.length]; + for (int i = 0; i < candidates.length; i++) { + candidateAnnotations[i] = MergedAnnotations.from(candidates[i]); + } + return candidateAnnotations; + } + + private static boolean isAutowiredPresent(MergedAnnotations[] candidateAnnotations) { + for (MergedAnnotations annotations : candidateAnnotations) { + if (annotations.isPresent(Autowired.class)) { + return true; + } + } + return false; } - Constructor[] constructors = type.getDeclaredConstructors(); - if (constructors.length == 1 && constructors[0].getParameterCount() > 0) { - return constructors[0]; + + private static Constructor getConstructorBindingAnnotated(Class type, Constructor[] candidates, + MergedAnnotations[] mergedAnnotations) { + Constructor result = null; + for (int i = 0; i < candidates.length; i++) { + if (mergedAnnotations[i].isPresent(ConstructorBinding.class)) { + Assert.state(candidates[i].getParameterCount() > 0, + () -> type.getName() + " declares @ConstructorBinding on a no-args constructor"); + Assert.state(result == null, + () -> type.getName() + " has more than one @ConstructorBinding constructor"); + result = candidates[i]; + } + } + return result; + } - Constructor constructor = null; - for (Constructor candidate : constructors) { - if (!Modifier.isPrivate(candidate.getModifiers())) { - if (constructor != null) { - return null; + + private static Constructor deduceBindConstructor(Constructor[] candidates) { + if (candidates.length == 1 && candidates[0].getParameterCount() > 0) { + return candidates[0]; + } + Constructor result = null; + for (Constructor candidate : candidates) { + if (!Modifier.isPrivate(candidate.getModifiers())) { + if (result != null) { + return null; + } + result = candidate; } - constructor = candidate; } + return (result != null && result.getParameterCount() > 0) ? result : null; } - if (constructor != null && constructor.getParameterCount() > 0) { - return constructor; + + private static boolean isKotlinType(Class type) { + return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); } - return null; - } - private Constructor getDeducedKotlinConstructor(Class type) { - Constructor primaryConstructor = BeanUtils.findPrimaryConstructor(type); - if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { - return primaryConstructor; + private static Constructor deduceKotlinBindConstructor(Class type) { + Constructor primaryConstructor = BeanUtils.findPrimaryConstructor(type); + if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { + return primaryConstructor; + } + return null; } - return null; + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java index 7fd75262078..b0e8ad3b7de 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java @@ -40,6 +40,7 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; 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 3fad65e03a4..152d01d3a3d 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 @@ -27,7 +27,9 @@ 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.Bindable; +import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -227,8 +229,26 @@ class ConfigurationPropertiesBeanTests { Bindable target = propertiesBean.asBindTarget(); assertThat(target.getType()).isEqualTo(ResolvableType.forClass(ConstructorBindingOnConstructor.class)); assertThat(target.getValue()).isNull(); - assertThat(ConfigurationPropertiesBindConstructorProvider.INSTANCE - .getBindConstructor(ConstructorBindingOnConstructor.class, false)).isNotNull(); + assertThat(BindConstructorProvider.DEFAULT.getBindConstructor(ConstructorBindingOnConstructor.class, false)) + .isNotNull(); + } + + @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.getAnnotation()).isNotNull(); + Bindable target = propertiesBean.asBindTarget(); + assertThat(target.getType()) + .isEqualTo(ResolvableType.forClass(DeprecatedConstructorBindingOnConstructor.class)); + assertThat(target.getValue()).isNull(); + assertThat(BindConstructorProvider.DEFAULT.getBindConstructor(DeprecatedConstructorBindingOnConstructor.class, + false)).isNotNull(); } @Test @@ -249,8 +269,8 @@ class ConfigurationPropertiesBeanTests { Bindable target = propertiesBean.asBindTarget(); assertThat(target.getType()).isEqualTo(ResolvableType.forClass(implicitConstructorBinding)); assertThat(target.getValue()).isNull(); - Constructor bindConstructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE - .getBindConstructor(implicitConstructorBinding, false); + Constructor bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(implicitConstructorBinding, + false); assertThat(bindConstructor).isNotNull(); assertThat(bindConstructor.getParameterTypes()).containsExactly(String.class, Integer.class); } @@ -268,64 +288,64 @@ class ConfigurationPropertiesBeanTests { } @Test - void bindTypeForTypeWhenNoConstructorBindingReturnsJavaBean() { - BindMethod bindType = BindMethod.forType(NoConstructorBinding.class); + void bindMethodGetWhenNoConstructorBindingReturnsJavaBean() { + BindMethod bindType = BindMethod.get(NoConstructorBinding.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @Test - void bindTypeForTypeWhenConstructorBindingOnConstructorReturnsValueObject() { - BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class); + void bindMethodGetWhenConstructorBindingOnConstructorReturnsValueObject() { + BindMethod bindType = BindMethod.get(ConstructorBindingOnConstructor.class); assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT); } @Test - void bindTypeForTypeWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() { - BindMethod bindType = BindMethod.forType(ConstructorBindingNoAnnotation.class); + void bindMethodGetWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() { + BindMethod bindType = BindMethod.get(ConstructorBindingNoAnnotation.class); assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT); } @Test - void bindTypeForTypeWhenConstructorBindingOnMultipleConstructorsThrowsException() { + void bindMethodGetWhenConstructorBindingOnMultipleConstructorsThrowsException() { assertThatIllegalStateException() - .isThrownBy(() -> BindMethod.forType(ConstructorBindingOnMultipleConstructors.class)) + .isThrownBy(() -> BindMethod.get(ConstructorBindingOnMultipleConstructors.class)) .withMessage(ConstructorBindingOnMultipleConstructors.class.getName() + " has more than one @ConstructorBinding constructor"); } @Test - void bindTypeForTypeWithMultipleConstructorsReturnJavaBean() { - BindMethod bindType = BindMethod.forType(NoConstructorBindingOnMultipleConstructors.class); + void bindMethodGetWithMultipleConstructorsReturnJavaBean() { + BindMethod bindType = BindMethod.get(NoConstructorBindingOnMultipleConstructors.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @Test - void bindTypeForTypeWithNoArgConstructorReturnsJavaBean() { - BindMethod bindType = BindMethod.forType(JavaBeanWithNoArgConstructor.class); + void bindMethodGetWithNoArgConstructorReturnsJavaBean() { + BindMethod bindType = BindMethod.get(JavaBeanWithNoArgConstructor.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @Test - void bindTypeForTypeWithSingleArgAutowiredConstructorReturnsJavaBean() { - BindMethod bindType = BindMethod.forType(JavaBeanWithAutowiredConstructor.class); + void bindMethodGetWithSingleArgAutowiredConstructorReturnsJavaBean() { + BindMethod bindType = BindMethod.get(JavaBeanWithAutowiredConstructor.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @Test void constructorBindingAndAutowiredConstructorsShouldThrowException() { assertThatIllegalStateException() - .isThrownBy(() -> BindMethod.forType(ConstructorBindingAndAutowiredConstructors.class)); + .isThrownBy(() -> BindMethod.get(ConstructorBindingAndAutowiredConstructors.class)); } @Test void innerClassWithSyntheticFieldShouldReturnJavaBean() { - BindMethod bindType = BindMethod.forType(Inner.class); + BindMethod bindType = BindMethod.get(Inner.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @Test void innerClassWithParameterizedConstructorShouldReturnJavaBean() { - BindMethod bindType = BindMethod.forType(ParameterizedConstructorInner.class); + BindMethod bindType = BindMethod.get(ParameterizedConstructorInner.class); assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN); } @@ -533,6 +553,20 @@ class ConfigurationPropertiesBeanTests { } + @ConfigurationProperties + @SuppressWarnings("removal") + static class DeprecatedConstructorBindingOnConstructor { + + DeprecatedConstructorBindingOnConstructor(String name) { + this(name, -1); + } + + @org.springframework.boot.context.properties.ConstructorBinding + DeprecatedConstructorBindingOnConstructor(String name, int age) { + } + + } + @ConfigurationProperties static class ConstructorBindingOnMultipleConstructors { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 706463529f3..f4077af3c75 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -54,6 +54,7 @@ import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.properties.bind.BindException; +import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzerTests.java index 0cc9620de32..b09d304a36f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzerTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.bind.ConstructorBinding; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter; import org.springframework.context.annotation.AnnotationConfigApplicationContext; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java new file mode 100644 index 00000000000..e6d0e904515 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProviderTests.java @@ -0,0 +1,173 @@ +/* + * Copyright 2012-2022 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; + +import java.lang.reflect.Constructor; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link DefaultBindConstructorProvider}. + * + * @author Phillip Webb + * @author Madhura Bhave + */ +class DefaultBindConstructorProviderTests { + + private DefaultBindConstructorProvider provider = new DefaultBindConstructorProvider(); + + @Test + void getBindConstructorWhenHasOnlyDefaultConstructorReturnsNull() { + Constructor constructor = this.provider.getBindConstructor(OnlyDefaultConstructor.class, false); + assertThat(constructor).isNull(); + } + + @Test + void getBindConstructorWhenHasMultipleAmbiguousConstructorsReturnsNull() { + Constructor constructor = this.provider.getBindConstructor(MultipleAmbiguousConstructors.class, false); + assertThat(constructor).isNull(); + } + + @Test + void getBindConstructorWhenHasTwoConstructorsWithOneConstructorBindingReturnsConstructor() { + Constructor constructor = this.provider.getBindConstructor(TwoConstructorsWithOneConstructorBinding.class, + false); + assertThat(constructor).isNotNull(); + assertThat(constructor.getParameterCount()).isEqualTo(1); + } + + @Test + void getBindConstructorWhenHasOneConstructorWithAutowiredReturnsNull() { + Constructor constructor = this.provider.getBindConstructor(OneConstructorWithAutowired.class, false); + assertThat(constructor).isNull(); + } + + @Test + void getBindConstructorWhenHasTwoConstructorsWithOneAutowiredReturnsNull() { + Constructor constructor = this.provider.getBindConstructor(TwoConstructorsWithOneAutowired.class, false); + assertThat(constructor).isNull(); + } + + @Test + void getBindConstructorWhenHasTwoConstructorsWithOneAutowiredAndOneConstructorBindingThrowsException() { + assertThatIllegalStateException() + .isThrownBy(() -> this.provider + .getBindConstructor(TwoConstructorsWithOneAutowiredAndOneConstructorBinding.class, false)) + .withMessageContaining("declares @ConstructorBinding and @Autowired"); + } + + @Test + void getBindConstructorWhenHasOneConstructorWithConstructorBindingReturnsConstructor() { + Constructor constructor = this.provider.getBindConstructor(OneConstructorWithConstructorBinding.class, + false); + assertThat(constructor).isNotNull(); + } + + @Test + void getBindConstructorWhenHasTwoConstructorsWithBothConstructorBindingThrowsException() { + assertThatIllegalStateException() + .isThrownBy( + () -> this.provider.getBindConstructor(TwoConstructorsWithBothConstructorBinding.class, false)) + .withMessageContaining("has more than one @ConstructorBinding"); + } + + static class OnlyDefaultConstructor { + + } + + static class MultipleAmbiguousConstructors { + + MultipleAmbiguousConstructors() { + } + + MultipleAmbiguousConstructors(String name) { + } + + } + + static class TwoConstructorsWithOneConstructorBinding { + + @ConstructorBinding + TwoConstructorsWithOneConstructorBinding(String name) { + this(name, 100); + } + + TwoConstructorsWithOneConstructorBinding(String name, int age) { + } + + } + + static class OneConstructorWithAutowired { + + @Autowired + OneConstructorWithAutowired(String name, int age) { + } + + } + + static class TwoConstructorsWithOneAutowired { + + @Autowired + TwoConstructorsWithOneAutowired(String name) { + this(name, 100); + } + + TwoConstructorsWithOneAutowired(String name, int age) { + } + + } + + static class TwoConstructorsWithOneAutowiredAndOneConstructorBinding { + + @Autowired + TwoConstructorsWithOneAutowiredAndOneConstructorBinding(String name) { + this(name, 100); + } + + @ConstructorBinding + TwoConstructorsWithOneAutowiredAndOneConstructorBinding(String name, int age) { + } + + } + + static class OneConstructorWithConstructorBinding { + + @ConstructorBinding + OneConstructorWithConstructorBinding(String name, int age) { + } + + } + + static class TwoConstructorsWithBothConstructorBinding { + + @ConstructorBinding + TwoConstructorsWithBothConstructorBinding(String name) { + this(name, 100); + } + + @ConstructorBinding + TwoConstructorsWithBothConstructorBinding(String name, int age) { + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index e2f7527b832..2873bc44385 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.context.properties.bind; -import java.io.IOException; import java.lang.reflect.Constructor; import java.nio.file.Path; import java.nio.file.Paths; @@ -366,7 +365,7 @@ class ValueObjectBinderTests { } @Test - void bindToRecordWithDefaultValue() throws IOException { + void bindToRecordWithDefaultValue() { MockConfigurationPropertySource source = new MockConfigurationPropertySource(); source.put("test.record.property1", "value-from-config-1"); this.sources.add(source); diff --git a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/bind/KotlinDefaultBindConstructorProviderTests.kt similarity index 88% rename from spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt rename to spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/bind/KotlinDefaultBindConstructorProviderTests.kt index ac5efd8dc51..576c3bcf4d3 100644 --- a/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/ConfigurationPropertiesBindConstructorProviderTests.kt +++ b/spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/bind/KotlinDefaultBindConstructorProviderTests.kt @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -// -package org.springframework.boot.context.properties; + +package org.springframework.boot.context.properties.bind; import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatIllegalStateException @@ -22,14 +22,14 @@ import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired /** - * Tests for `ConfigurationPropertiesBindConstructorProvider`. + * Tests for `DefaultBindConstructorProvider`. * * @author Madhura Bhave */ @Suppress("unused") -class ConfigurationPropertiesBindConstructorProviderTests { +class KotlinDefaultBindConstructorProviderTests { - private val constructorProvider = ConfigurationPropertiesBindConstructorProvider() + private val constructorProvider = DefaultBindConstructorProvider() @Test fun `type with default constructor should register java bean`() { @@ -119,66 +119,55 @@ class ConfigurationPropertiesBindConstructorProviderTests { assertThat(bindConstructor).isNotNull(); } - @ConfigurationProperties(prefix = "foo") class FooProperties - @ConfigurationProperties(prefix = "bar") class PrimaryWithAutowiredSecondaryProperties constructor(val name: String?, val counter: Int = 42) { @Autowired constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21) } - @ConfigurationProperties(prefix = "bar") class AutowiredSecondaryProperties { @Autowired constructor(@Suppress("UNUSED_PARAMETER") foo: String) } - @ConfigurationProperties(prefix = "bar") class AutowiredPrimaryProperties @Autowired constructor(val name: String?, val counter: Int = 42) { } - @ConfigurationProperties(prefix = "bar") class ConstructorBindingOnSecondaryAndAutowiredPrimaryProperties @Autowired constructor(val name: String?, val counter: Int = 42) { @ConstructorBinding constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21) } - @ConfigurationProperties(prefix = "bar") class ConstructorBindingOnPrimaryAndAutowiredSecondaryProperties @ConstructorBinding constructor(val name: String?, val counter: Int = 42) { @Autowired constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21) } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingOnSecondaryWithPrimaryConstructor constructor(val name: String?, val counter: Int = 42) { @ConstructorBinding constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21) } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingOnPrimaryWithSecondaryConstructor @ConstructorBinding constructor(val name: String?, val counter: Int = 42) { constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21) } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingPrimaryConstructorNoAnnotation(val name: String?, val counter: Int = 42) - @ConfigurationProperties(prefix = "bing") class ConstructorBindingSecondaryConstructorNoAnnotation { constructor(@Suppress("UNUSED_PARAMETER") foo: String) } - @ConfigurationProperties(prefix = "bing") class MultipleAmbiguousConstructors { constructor() @@ -187,7 +176,6 @@ class ConfigurationPropertiesBindConstructorProviderTests { } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingMultipleConstructors { constructor(@Suppress("UNUSED_PARAMETER") bar: Int) @@ -197,7 +185,6 @@ class ConfigurationPropertiesBindConstructorProviderTests { } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingMultipleAnnotatedConstructors { @ConstructorBinding @@ -208,7 +195,6 @@ class ConfigurationPropertiesBindConstructorProviderTests { } - @ConfigurationProperties(prefix = "bing") class ConstructorBindingSecondaryAndPrimaryAnnotatedConstructors @ConstructorBinding constructor(val name: String?, val counter: Int = 42) { @ConstructorBinding @@ -216,7 +202,6 @@ class ConfigurationPropertiesBindConstructorProviderTests { } - @ConfigurationProperties(prefix = "bing") data class ConstructorBindingDataClassWithDefaultValues(val name: String = "Joan", val counter: Int = 42) } \ No newline at end of file