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:
@@ -19,7 +19,6 @@ package org.springframework.boot.actuate.context.properties;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -52,7 +51,6 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizableData;
|
||||
import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
@@ -63,7 +61,8 @@ 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.ConstructorBinding;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindConstructorProvider;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Name;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
@@ -72,11 +71,9 @@ import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -472,7 +469,9 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
List<BeanPropertyWriter> beanProperties) {
|
||||
List<BeanPropertyWriter> result = new ArrayList<>();
|
||||
Class<?> beanClass = beanDesc.getType().getRawClass();
|
||||
Constructor<?> bindConstructor = findBindConstructor(ClassUtils.getUserClass(beanClass));
|
||||
Bindable<?> bindable = Bindable.of(ClassUtils.getUserClass(beanClass));
|
||||
Constructor<?> bindConstructor = ConfigurationPropertiesBindConstructorProvider.INSTANCE
|
||||
.getBindConstructor(bindable, false);
|
||||
for (BeanPropertyWriter writer : beanProperties) {
|
||||
if (isCandidate(beanDesc, writer, bindConstructor)) {
|
||||
result.add(writer);
|
||||
@@ -540,34 +539,6 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
|
||||
return StringUtils.capitalize(propertyName);
|
||||
}
|
||||
|
||||
private Constructor<?> findBindConstructor(Class<?> type) {
|
||||
boolean classConstructorBinding = MergedAnnotations
|
||||
.from(type, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)
|
||||
.isPresent(ConstructorBinding.class);
|
||||
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type)) {
|
||||
Constructor<?> constructor = BeanUtils.findPrimaryConstructor(type);
|
||||
if (constructor != null) {
|
||||
return findBindConstructor(classConstructorBinding, constructor);
|
||||
}
|
||||
}
|
||||
return findBindConstructor(classConstructorBinding, type.getDeclaredConstructors());
|
||||
}
|
||||
|
||||
private Constructor<?> findBindConstructor(boolean classConstructorBinding, Constructor<?>... candidates) {
|
||||
List<Constructor<?>> candidateConstructors = Arrays.stream(candidates)
|
||||
.filter((constructor) -> constructor.getParameterCount() > 0).collect(Collectors.toList());
|
||||
List<Constructor<?>> flaggedConstructors = candidateConstructors.stream()
|
||||
.filter((candidate) -> MergedAnnotations.from(candidate).isPresent(ConstructorBinding.class))
|
||||
.collect(Collectors.toList());
|
||||
if (flaggedConstructors.size() == 1) {
|
||||
return flaggedConstructors.get(0);
|
||||
}
|
||||
if (classConstructorBinding && candidateConstructors.size() == 1) {
|
||||
return candidateConstructors.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesBeanDescriptor;
|
||||
import org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.ContextConfigurationProperties;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
@@ -72,6 +73,12 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
(properties) -> assertThat(properties).containsOnlyKeys("dbPassword", "myTestProperty", "duration")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void descriptorWithAutowiredConstructorBindMethodDetectsRelevantProperties() {
|
||||
this.contextRunner.withUserConfiguration(AutowiredPropertiesConfiguration.class)
|
||||
.run(assertProperties("autowired", (properties) -> assertThat(properties).containsOnlyKeys("counter")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void descriptorWithValueObjectBindMethodDetectsRelevantProperties() {
|
||||
this.contextRunner.withUserConfiguration(ImmutablePropertiesConfiguration.class).run(assertProperties(
|
||||
@@ -489,7 +496,6 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "immutable")
|
||||
@ConstructorBinding
|
||||
public static class ImmutableProperties {
|
||||
|
||||
private final String dbPassword;
|
||||
@@ -540,7 +546,6 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "multiconstructor")
|
||||
@ConstructorBinding
|
||||
public static class MultiConstructorProperties {
|
||||
|
||||
private final String name;
|
||||
@@ -568,6 +573,43 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(AutowiredProperties.class)
|
||||
static class AutowiredPropertiesConfiguration {
|
||||
|
||||
@Bean
|
||||
String hello() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties(prefix = "autowired")
|
||||
public static class AutowiredProperties {
|
||||
|
||||
private final String name;
|
||||
|
||||
private int counter;
|
||||
|
||||
@Autowired
|
||||
AutowiredProperties(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return this.counter;
|
||||
}
|
||||
|
||||
public void setCounter(int counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(ImmutableNestedProperties.class)
|
||||
static class ImmutableNestedPropertiesConfiguration {
|
||||
@@ -575,7 +617,6 @@ class ConfigurationPropertiesReportEndpointTests {
|
||||
}
|
||||
|
||||
@ConfigurationProperties("immutablenested")
|
||||
@ConstructorBinding
|
||||
public static class ImmutableNestedProperties {
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.boot.actuate.context.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.ConstructorBinding;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
@@ -27,7 +26,6 @@ import org.springframework.validation.annotation.Validated;
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
@Validated
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "validated")
|
||||
public class ValidatedConstructorBindingProperties {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user