Allow @ConstructorBinding to be optional
This commit makes @ConstructorBinding optional for a type that has a single parameterized constructor. An @Autowired annotation on any of the constructors indicates that the type should not be constructor bound. Since @ConstructorBinding is now deduced for a single parameterized constructor, the annotation is no longer needed at the type level. Closes gh-23216
This commit is contained in:
@@ -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.
|
||||
@@ -71,11 +71,16 @@ public final class ConfigurationPropertiesBean {
|
||||
|
||||
private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation,
|
||||
Bindable<?> bindTarget) {
|
||||
this(name, instance, annotation, bindTarget, BindMethod.forType(bindTarget.getType().resolve()));
|
||||
}
|
||||
|
||||
private ConfigurationPropertiesBean(String name, Object instance, ConfigurationProperties annotation,
|
||||
Bindable<?> bindTarget, BindMethod bindMethod) {
|
||||
this.name = name;
|
||||
this.instance = instance;
|
||||
this.annotation = annotation;
|
||||
this.bindTarget = bindTarget;
|
||||
this.bindMethod = BindMethod.forType(bindTarget.getType().resolve());
|
||||
this.bindMethod = bindMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,6 +272,9 @@ public final class ConfigurationPropertiesBean {
|
||||
if (instance != null) {
|
||||
bindTarget = bindTarget.withExistingValue(instance);
|
||||
}
|
||||
if (factory != null) {
|
||||
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget, BindMethod.JAVA_BEAN);
|
||||
}
|
||||
return new ConfigurationPropertiesBean(name, instance, annotation, bindTarget);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -17,11 +17,11 @@
|
||||
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;
|
||||
|
||||
@@ -31,10 +31,14 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider {
|
||||
public class ConfigurationPropertiesBindConstructorProvider implements BindConstructorProvider {
|
||||
|
||||
static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
|
||||
/**
|
||||
* A shared singleton {@link ConfigurationPropertiesBindConstructorProvider} instance.
|
||||
*/
|
||||
public static final ConfigurationPropertiesBindConstructorProvider INSTANCE = new ConfigurationPropertiesBindConstructorProvider();
|
||||
|
||||
@Override
|
||||
public Constructor<?> getBindConstructor(Bindable<?> bindable, boolean isNestedConstructorBinding) {
|
||||
@@ -45,26 +49,88 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
Constructor<?> constructor = findConstructorBindingAnnotatedConstructor(type);
|
||||
if (constructor == null && (isConstructorBindingType(type) || isNestedConstructorBinding)) {
|
||||
constructor = deduceBindConstructor(type);
|
||||
Constructors constructors = Constructors.getConstructors(type);
|
||||
if (constructors.getBind() != null || isNestedConstructorBinding) {
|
||||
Assert.state(!constructors.hasAutowired(),
|
||||
() -> type.getName() + " declares @ConstructorBinding and @Autowired constructor");
|
||||
}
|
||||
return constructor;
|
||||
return constructors.getBind();
|
||||
}
|
||||
|
||||
private Constructor<?> findConstructorBindingAnnotatedConstructor(Class<?> type) {
|
||||
if (isKotlinType(type)) {
|
||||
Constructor<?> constructor = BeanUtils.findPrimaryConstructor(type);
|
||||
if (constructor != null) {
|
||||
return findAnnotatedConstructor(type, constructor);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
return findAnnotatedConstructor(type, type.getDeclaredConstructors());
|
||||
}
|
||||
|
||||
private Constructor<?> findAnnotatedConstructor(Class<?> type, Constructor<?>... candidates) {
|
||||
Constructor<?> constructor = null;
|
||||
for (Constructor<?> candidate : candidates) {
|
||||
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");
|
||||
@@ -72,45 +138,9 @@ class ConfigurationPropertiesBindConstructorProvider implements BindConstructorP
|
||||
() -> type.getName() + " has more than one @ConstructorBinding constructor");
|
||||
constructor = candidate;
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
private boolean isConstructorBindingType(Class<?> type) {
|
||||
return isImplicitConstructorBindingType(type) || isConstructorBindingAnnotatedType(type);
|
||||
}
|
||||
|
||||
private boolean isImplicitConstructorBindingType(Class<?> type) {
|
||||
Class<?> superclass = type.getSuperclass();
|
||||
return (superclass != null) && "java.lang.Record".equals(superclass.getName());
|
||||
}
|
||||
|
||||
private boolean isConstructorBindingAnnotatedType(Class<?> type) {
|
||||
return MergedAnnotations.from(type, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
|
||||
.isPresent(ConstructorBinding.class);
|
||||
}
|
||||
|
||||
private Constructor<?> deduceBindConstructor(Class<?> type) {
|
||||
if (isKotlinType(type)) {
|
||||
return deducedKotlinBindConstructor(type);
|
||||
}
|
||||
Constructor<?>[] constructors = type.getDeclaredConstructors();
|
||||
if (constructors.length == 1 && constructors[0].getParameterCount() > 0) {
|
||||
return constructors[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Constructor<?> deducedKotlinBindConstructor(Class<?> type) {
|
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(type);
|
||||
if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) {
|
||||
return primaryConstructor;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isKotlinType(Class<?> type) {
|
||||
return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that can be used to indicate that configuration properties should be bound
|
||||
* using constructor arguments rather than by calling setters. Can be added at the type
|
||||
* level (if there is an unambiguous constructor) or on the actual constructor to use.
|
||||
* 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`.
|
||||
* <p>
|
||||
* Note: To use constructor binding the class must be enabled using
|
||||
* {@link EnableConfigurationProperties @EnableConfigurationProperties} or configuration
|
||||
@@ -39,7 +40,7 @@ import java.lang.annotation.Target;
|
||||
* @since 2.2.0
|
||||
* @see ConfigurationProperties
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR })
|
||||
@Target(ElementType.CONSTRUCTOR)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ConstructorBinding {
|
||||
|
||||
@@ -102,7 +102,6 @@ class ConfigurationPropertiesBeanRegistrarTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("valuecp")
|
||||
static class ValueObjectConfigurationProperties {
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -27,6 +27,7 @@ import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
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.Bindable;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
@@ -47,6 +48,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
* Tests for {@link ConfigurationPropertiesBean}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class ConfigurationPropertiesBeanTests {
|
||||
|
||||
@@ -266,14 +268,14 @@ class ConfigurationPropertiesBeanTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTypeForTypeWhenNoConstructorBindingOnTypeReturnsValueObject() {
|
||||
BindMethod bindType = BindMethod.forType(ConstructorBindingOnType.class);
|
||||
void bindTypeForTypeWhenConstructorBindingOnConstructorReturnsValueObject() {
|
||||
BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTypeForTypeWhenNoConstructorBindingOnConstructorReturnsValueObject() {
|
||||
BindMethod bindType = BindMethod.forType(ConstructorBindingOnConstructor.class);
|
||||
void bindTypeForTypeWhenNoConstructorBindingAnnotationOnSingleParameterizedConstructorReturnsValueObject() {
|
||||
BindMethod bindType = BindMethod.forType(ConstructorBindingNoAnnotation.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.VALUE_OBJECT);
|
||||
}
|
||||
|
||||
@@ -285,6 +287,42 @@ class ConfigurationPropertiesBeanTests {
|
||||
+ " has more than one @ConstructorBinding constructor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTypeForTypeWithMultipleConstructorsReturnJavaBean() {
|
||||
BindMethod bindType = BindMethod.forType(NoConstructorBindingOnMultipleConstructors.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTypeForTypeWithNoArgConstructorReturnsJavaBean() {
|
||||
BindMethod bindType = BindMethod.forType(JavaBeanWithNoArgConstructor.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTypeForTypeWithSingleArgAutowiredConstructorReturnsJavaBean() {
|
||||
BindMethod bindType = BindMethod.forType(JavaBeanWithAutowiredConstructor.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorBindingAndAutowiredConstructorsShouldThrowException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> BindMethod.forType(ConstructorBindingAndAutowiredConstructors.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerClassWithSyntheticFieldShouldReturnJavaBean() {
|
||||
BindMethod bindType = BindMethod.forType(Inner.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerClassWithParameterizedConstructorShouldReturnJavaBean() {
|
||||
BindMethod bindType = BindMethod.forType(ParameterizedConstructorInner.class);
|
||||
assertThat(bindType).isEqualTo(BindMethod.JAVA_BEAN);
|
||||
}
|
||||
|
||||
private void get(Class<?> configuration, String beanName, ThrowingConsumer<ConfigurationPropertiesBean> consumer)
|
||||
throws Throwable {
|
||||
get(configuration, beanName, true, consumer);
|
||||
@@ -448,7 +486,6 @@ class ConfigurationPropertiesBeanTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
@ConstructorBinding
|
||||
static class ValueObject {
|
||||
|
||||
ValueObject(String name) {
|
||||
@@ -470,10 +507,9 @@ class ConfigurationPropertiesBeanTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
@ConstructorBinding
|
||||
static class ConstructorBindingOnType {
|
||||
static class ConstructorBindingNoAnnotation {
|
||||
|
||||
ConstructorBindingOnType(String name) {
|
||||
ConstructorBindingNoAnnotation(String name) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -505,6 +541,48 @@ class ConfigurationPropertiesBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
static class NoConstructorBindingOnMultipleConstructors {
|
||||
|
||||
NoConstructorBindingOnMultipleConstructors(String name) {
|
||||
this(name, -1);
|
||||
}
|
||||
|
||||
NoConstructorBindingOnMultipleConstructors(String name, int age) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
static class JavaBeanWithAutowiredConstructor {
|
||||
|
||||
@Autowired
|
||||
JavaBeanWithAutowiredConstructor(String name) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
static class JavaBeanWithNoArgConstructor {
|
||||
|
||||
JavaBeanWithNoArgConstructor() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
static class ConstructorBindingAndAutowiredConstructors {
|
||||
|
||||
@Autowired
|
||||
ConstructorBindingAndAutowiredConstructors(String name) {
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
ConstructorBindingAndAutowiredConstructors(Integer age) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(NonAnnotatedBeanConfigurationImportSelector.class)
|
||||
static class NonAnnotatedBeanImportConfiguration {
|
||||
@@ -520,4 +598,17 @@ class ConfigurationPropertiesBeanTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
class Inner {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
class ParameterizedConstructorInner {
|
||||
|
||||
ParameterizedConstructorInner(Integer age) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1049,6 +1049,16 @@ class ConfigurationPropertiesTests {
|
||||
assertThat(bean.getNested().getOuter().getAge()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenConstructorBindingWithOuterClassAndNestedAutowiredShouldThrowException() {
|
||||
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
|
||||
Map<String, Object> source = new HashMap<>();
|
||||
source.put("test.nested.age", "5");
|
||||
sources.addLast(new MapPropertySource("test", source));
|
||||
assertThatExceptionOfType(ConfigurationPropertiesBindException.class).isThrownBy(
|
||||
() -> load(ConstructorBindingWithOuterClassConstructorBoundAndNestedAutowiredConfiguration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenConfigurationPropertiesPrefixMatchesPropertyInEnvironment() {
|
||||
MutablePropertySources sources = this.context.getEnvironment().getPropertySources();
|
||||
@@ -2092,7 +2102,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
static class OtherInjectedProperties {
|
||||
|
||||
@@ -2110,7 +2119,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
@Validated
|
||||
static class ConstructorParameterProperties {
|
||||
@@ -2135,7 +2143,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
static class ConstructorParameterWithUnitProperties {
|
||||
|
||||
@@ -2167,7 +2174,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
static class ConstructorParameterWithFormatProperties {
|
||||
|
||||
@@ -2192,7 +2198,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "test")
|
||||
@Validated
|
||||
static class ConstructorParameterValidatedProperties {
|
||||
@@ -2376,7 +2381,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("test")
|
||||
static class NestedConstructorProperties {
|
||||
|
||||
@@ -2414,7 +2418,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("test")
|
||||
static class NestedMultipleConstructorProperties {
|
||||
|
||||
@@ -2463,7 +2466,6 @@ class ConfigurationPropertiesTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
@ConstructorBinding
|
||||
static class ConstructorBindingWithOuterClassConstructorBoundProperties {
|
||||
|
||||
private final Nested nested;
|
||||
@@ -2492,6 +2494,36 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
static class ConstructorBindingWithOuterClassConstructorBoundAndNestedAutowired {
|
||||
|
||||
private final Nested nested;
|
||||
|
||||
ConstructorBindingWithOuterClassConstructorBoundAndNestedAutowired(Nested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
|
||||
private int age;
|
||||
|
||||
@Autowired
|
||||
Nested(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Outer {
|
||||
|
||||
private int age;
|
||||
@@ -2511,6 +2543,11 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableConfigurationProperties(ConstructorBindingWithOuterClassConstructorBoundAndNestedAutowired.class)
|
||||
static class ConstructorBindingWithOuterClassConstructorBoundAndNestedAutowiredConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
static class MultiConstructorConfigurationListProperties {
|
||||
|
||||
@@ -2613,7 +2650,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("test")
|
||||
static class SyntheticNestedConstructorProperties {
|
||||
|
||||
@@ -2667,7 +2703,6 @@ class ConfigurationPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("test")
|
||||
static class DeducedNestedConstructorProperties {
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class EnableConfigurationPropertiesRegistrarTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeWithConstructorBindingShouldRegisterConfigurationPropertiesBeanDefinition() {
|
||||
void constructorBoundPropertiesShouldRegisterConfigurationPropertiesBeanDefinition() {
|
||||
register(TestConfiguration.class);
|
||||
BeanDefinition definition = this.beanFactory
|
||||
.getBeanDefinition("bar-" + getClass().getName() + "$BarProperties");
|
||||
@@ -137,7 +137,6 @@ class EnableConfigurationPropertiesRegistrarTests {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "bar")
|
||||
static class BarProperties {
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.context.properties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.diagnostics.FailureAnalysis;
|
||||
import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
@@ -75,7 +76,6 @@ class NotConstructorBoundInjectionFailureAnalyzerTests {
|
||||
return analysis;
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties("test")
|
||||
static class ConstructorBoundProperties {
|
||||
|
||||
@@ -102,6 +102,7 @@ class NotConstructorBoundInjectionFailureAnalyzerTests {
|
||||
|
||||
private String name;
|
||||
|
||||
@Autowired
|
||||
JavaBeanBoundProperties(String dependency) {
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.boot.context.properties.scan.valid;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.scan.valid.b.BScanConfiguration;
|
||||
|
||||
@@ -47,7 +46,6 @@ public class ConfigurationPropertiesScanConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "bar")
|
||||
static class BarProperties {
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.context.properties.scan.valid.b;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
|
||||
/**
|
||||
* @author Madhura Bhave
|
||||
@@ -29,7 +28,6 @@ public class BScanConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "b.first")
|
||||
public static class BFirstProperties implements BProperties {
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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 org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatIllegalStateException
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
|
||||
/**
|
||||
* Tests for `ConfigurationPropertiesBindConstructorProvider`.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class ConfigurationPropertiesBindConstructorProviderTests {
|
||||
|
||||
private val constructorProvider = ConfigurationPropertiesBindConstructorProvider()
|
||||
|
||||
@Test
|
||||
fun `type with default constructor should register java bean`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(FooProperties::class.java, false)
|
||||
assertThat(bindConstructor).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with no primary constructor should register java bean`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(MultipleAmbiguousConstructors::class.java, false)
|
||||
assertThat(bindConstructor).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with primary and secondary annotated constructor should use secondary constructor for binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(ConstructorBindingOnSecondaryWithPrimaryConstructor::class.java, false)
|
||||
assertThat(bindConstructor).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with primary constructor with autowired should not use constructor binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(AutowiredPrimaryProperties::class.java, false)
|
||||
assertThat(bindConstructor).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with primary and secondary constructor with autowired should not use constructor binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(PrimaryWithAutowiredSecondaryProperties::class.java, false)
|
||||
assertThat(bindConstructor).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with autowired secondary constructor should not use constructor binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(AutowiredSecondaryProperties::class.java, false)
|
||||
assertThat(bindConstructor).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with autowired primary and constructor binding on secondary constructor should throw exception`() {
|
||||
assertThatIllegalStateException().isThrownBy {
|
||||
this.constructorProvider.getBindConstructor(ConstructorBindingOnSecondaryAndAutowiredPrimaryProperties::class.java, false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with autowired secondary and constructor binding on primary constructor should throw exception`() {
|
||||
assertThatIllegalStateException().isThrownBy {
|
||||
this.constructorProvider.getBindConstructor(ConstructorBindingOnPrimaryAndAutowiredSecondaryProperties::class.java, false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with primary constructor and no annotation should use constructor binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(ConstructorBindingPrimaryConstructorNoAnnotation::class.java, false)
|
||||
assertThat(bindConstructor).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with secondary constructor and no annotation should use constructor binding`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(ConstructorBindingSecondaryConstructorNoAnnotation::class.java, false)
|
||||
assertThat(bindConstructor).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with multiple constructors`() {
|
||||
val bindConstructor = this.constructorProvider.getBindConstructor(ConstructorBindingMultipleConstructors::class.java, false)
|
||||
assertThat(bindConstructor).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with multiple annotated constructors should throw exception`() {
|
||||
assertThatIllegalStateException().isThrownBy {
|
||||
this.constructorProvider.getBindConstructor(ConstructorBindingMultipleAnnotatedConstructors::class.java, false)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `type with secondary and primary annotated constructors should throw exception`() {
|
||||
assertThatIllegalStateException().isThrownBy {
|
||||
this.constructorProvider.getBindConstructor(ConstructorBindingSecondaryAndPrimaryAnnotatedConstructors::class.java, false)
|
||||
}
|
||||
}
|
||||
|
||||
@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()
|
||||
|
||||
constructor(@Suppress("UNUSED_PARAMETER") foo: String)
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bing")
|
||||
class ConstructorBindingMultipleConstructors {
|
||||
|
||||
constructor(@Suppress("UNUSED_PARAMETER") bar: Int)
|
||||
|
||||
@ConstructorBinding
|
||||
constructor(@Suppress("UNUSED_PARAMETER") foo: String)
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bing")
|
||||
class ConstructorBindingMultipleAnnotatedConstructors {
|
||||
|
||||
@ConstructorBinding
|
||||
constructor(@Suppress("UNUSED_PARAMETER") bar: Int)
|
||||
|
||||
@ConstructorBinding
|
||||
constructor(@Suppress("UNUSED_PARAMETER") foo: String)
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "bing")
|
||||
class ConstructorBindingSecondaryAndPrimaryAnnotatedConstructors @ConstructorBinding constructor(val name: String?, val counter: Int = 42) {
|
||||
|
||||
@ConstructorBinding
|
||||
constructor(@Suppress("UNUSED_PARAMETER") foo: String) : this(foo, 21)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
|
||||
"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)
|
||||
.isEqualTo(ConfigurationPropertiesBean.BindMethod.VALUE_OBJECT)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -46,7 +46,6 @@ class KotlinConfigurationPropertiesBeanRegistrarTests {
|
||||
@ConfigurationProperties(prefix = "foo")
|
||||
class FooProperties
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "bar")
|
||||
class BarProperties(val name: String?, val counter: Int = 42)
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class KotlinConfigurationPropertiesTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "foo")
|
||||
@ConstructorBinding
|
||||
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String) {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user