Use @ConstructorBinding when generating meta-data
Update the configuration processor to use the newly introduced `@ConstructorBinding` annotation to determine when meta data should be generated from constructor parameters. Prior to this commit, the processor had no good way to tell when constructor parameters should be used instead of getters/setters. Closes gh-17035
This commit is contained in:
parent
4208c93ba7
commit
45f6668d03
|
|
@ -22,7 +22,6 @@ import java.io.StringWriter;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -39,7 +38,6 @@ import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.Modifier;
|
import javax.lang.model.element.Modifier;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
import javax.lang.model.element.VariableElement;
|
import javax.lang.model.element.VariableElement;
|
||||||
import javax.lang.model.type.DeclaredType;
|
|
||||||
import javax.lang.model.type.TypeKind;
|
import javax.lang.model.type.TypeKind;
|
||||||
import javax.lang.model.util.ElementFilter;
|
import javax.lang.model.util.ElementFilter;
|
||||||
import javax.tools.Diagnostic.Kind;
|
import javax.tools.Diagnostic.Kind;
|
||||||
|
|
@ -73,6 +71,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
|
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
|
||||||
+ "context.properties.DeprecatedConfigurationProperty";
|
+ "context.properties.DeprecatedConfigurationProperty";
|
||||||
|
|
||||||
|
static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.ConstructorBinding";
|
||||||
|
|
||||||
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.context.properties.bind.DefaultValue";
|
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.context.properties.bind.DefaultValue";
|
||||||
|
|
||||||
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.Endpoint";
|
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.Endpoint";
|
||||||
|
|
@ -101,6 +101,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String constructorBindingAnnotation() {
|
||||||
|
return CONSTRUCTOR_BINDING_ANNOTATION;
|
||||||
|
}
|
||||||
|
|
||||||
protected String defaultValueAnnotation() {
|
protected String defaultValueAnnotation() {
|
||||||
return DEFAULT_VALUE_ANNOTATION;
|
return DEFAULT_VALUE_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +134,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
|
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
|
||||||
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
|
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
|
||||||
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
|
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
|
||||||
defaultValueAnnotation(), endpointAnnotation(), readOperationAnnotation());
|
constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(),
|
||||||
|
readOperationAnnotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -159,38 +164,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
|
|
||||||
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv,
|
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv,
|
||||||
TypeElement annotation) {
|
TypeElement annotation) {
|
||||||
DeclaredType annotationType = (DeclaredType) annotation.asType();
|
|
||||||
Map<Element, List<Element>> result = new LinkedHashMap<>();
|
Map<Element, List<Element>> result = new LinkedHashMap<>();
|
||||||
for (Element element : roundEnv.getRootElements()) {
|
for (Element element : roundEnv.getRootElements()) {
|
||||||
LinkedList<Element> stack = new LinkedList<>();
|
List<Element> annotations = this.metadataEnv.getElementsAnnotatedOrMetaAnnotatedWith(element, annotation);
|
||||||
stack.push(element);
|
if (!annotations.isEmpty()) {
|
||||||
collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack);
|
result.put(element, annotations);
|
||||||
stack.removeFirst();
|
|
||||||
if (!stack.isEmpty()) {
|
|
||||||
result.put(element, Collections.unmodifiableList(stack));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean collectElementsAnnotatedOrMetaAnnotatedWith(DeclaredType annotationType,
|
|
||||||
LinkedList<Element> stack) {
|
|
||||||
Element element = stack.peekLast();
|
|
||||||
for (AnnotationMirror annotation : this.processingEnv.getElementUtils().getAllAnnotationMirrors(element)) {
|
|
||||||
Element annotationElement = annotation.getAnnotationType().asElement();
|
|
||||||
if (!stack.contains(annotationElement)) {
|
|
||||||
stack.addLast(annotationElement);
|
|
||||||
if (annotationElement.equals(annotationType.asElement())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) {
|
|
||||||
stack.removeLast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processElement(Element element) {
|
private void processElement(Element element) {
|
||||||
try {
|
try {
|
||||||
AnnotationMirror annotation = this.metadataEnv.getConfigurationPropertiesAnnotation(element);
|
AnnotationMirror annotation = this.metadataEnv.getConfigurationPropertiesAnnotation(element);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -86,6 +87,8 @@ class MetadataGenerationEnvironment {
|
||||||
|
|
||||||
private final String deprecatedConfigurationPropertyAnnotation;
|
private final String deprecatedConfigurationPropertyAnnotation;
|
||||||
|
|
||||||
|
private final String constructorBindingAnnotation;
|
||||||
|
|
||||||
private final String defaultValueAnnotation;
|
private final String defaultValueAnnotation;
|
||||||
|
|
||||||
private final String endpointAnnotation;
|
private final String endpointAnnotation;
|
||||||
|
|
@ -94,7 +97,8 @@ class MetadataGenerationEnvironment {
|
||||||
|
|
||||||
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
|
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
|
||||||
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
|
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
|
||||||
String defaultValueAnnotation, String endpointAnnotation, String readOperationAnnotation) {
|
String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation,
|
||||||
|
String readOperationAnnotation) {
|
||||||
this.typeUtils = new TypeUtils(environment);
|
this.typeUtils = new TypeUtils(environment);
|
||||||
this.elements = environment.getElementUtils();
|
this.elements = environment.getElementUtils();
|
||||||
this.messager = environment.getMessager();
|
this.messager = environment.getMessager();
|
||||||
|
|
@ -102,6 +106,7 @@ class MetadataGenerationEnvironment {
|
||||||
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
||||||
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
||||||
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
||||||
|
this.constructorBindingAnnotation = constructorBindingAnnotation;
|
||||||
this.defaultValueAnnotation = defaultValueAnnotation;
|
this.defaultValueAnnotation = defaultValueAnnotation;
|
||||||
this.endpointAnnotation = endpointAnnotation;
|
this.endpointAnnotation = endpointAnnotation;
|
||||||
this.readOperationAnnotation = readOperationAnnotation;
|
this.readOperationAnnotation = readOperationAnnotation;
|
||||||
|
|
@ -170,6 +175,14 @@ class MetadataGenerationEnvironment {
|
||||||
return new ItemDeprecation(reason, replacement);
|
return new ItemDeprecation(reason, replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasConstructorBindingAnnotation(TypeElement typeElement) {
|
||||||
|
return hasAnnotationRecursive(typeElement, this.constructorBindingAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasConstructorBindingAnnotation(ExecutableElement element) {
|
||||||
|
return hasAnnotation(element, this.constructorBindingAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
boolean hasAnnotation(Element element, String type) {
|
boolean hasAnnotation(Element element, String type) {
|
||||||
return getAnnotation(element, type) != null;
|
return getAnnotation(element, type) != null;
|
||||||
}
|
}
|
||||||
|
|
@ -185,6 +198,42 @@ class MetadataGenerationEnvironment {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the annotations that are annotated or meta-annotated with the specified
|
||||||
|
* {@link TypeElement annotation}.
|
||||||
|
* @param element the element to inspect
|
||||||
|
* @param annotationType the annotation to discover
|
||||||
|
* @return the annotations that are annotated or meta-annotated with this annotation
|
||||||
|
*/
|
||||||
|
List<Element> getElementsAnnotatedOrMetaAnnotatedWith(Element element, TypeElement annotationType) {
|
||||||
|
LinkedList<Element> stack = new LinkedList<>();
|
||||||
|
stack.push(element);
|
||||||
|
collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack);
|
||||||
|
stack.removeFirst();
|
||||||
|
return Collections.unmodifiableList(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAnnotationRecursive(Element element, String type) {
|
||||||
|
return !getElementsAnnotatedOrMetaAnnotatedWith(element, this.elements.getTypeElement(type)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean collectElementsAnnotatedOrMetaAnnotatedWith(TypeElement annotationType, LinkedList<Element> stack) {
|
||||||
|
Element element = stack.peekLast();
|
||||||
|
for (AnnotationMirror annotation : this.elements.getAllAnnotationMirrors(element)) {
|
||||||
|
Element annotationElement = annotation.getAnnotationType().asElement();
|
||||||
|
if (!stack.contains(annotationElement)) {
|
||||||
|
stack.addLast(annotationElement);
|
||||||
|
if (annotationElement.equals(annotationType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) {
|
||||||
|
stack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
||||||
Map<String, Object> values = new LinkedHashMap<>();
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
annotation.getElementValues()
|
annotation.getElementValues()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
|
@ -31,6 +32,7 @@ import javax.lang.model.util.ElementFilter;
|
||||||
* Resolve {@link PropertyDescriptor} instances.
|
* Resolve {@link PropertyDescriptor} instances.
|
||||||
*
|
*
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
class PropertyDescriptorResolver {
|
class PropertyDescriptorResolver {
|
||||||
|
|
||||||
|
|
@ -54,13 +56,19 @@ class PropertyDescriptorResolver {
|
||||||
if (factoryMethod != null) {
|
if (factoryMethod != null) {
|
||||||
return resolveJavaBeanProperties(type, factoryMethod, members);
|
return resolveJavaBeanProperties(type, factoryMethod, members);
|
||||||
}
|
}
|
||||||
ExecutableElement constructor = resolveConstructor(type);
|
return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), factoryMethod, members);
|
||||||
if (constructor != null) {
|
}
|
||||||
return resolveConstructorProperties(type, factoryMethod, members, constructor);
|
|
||||||
}
|
private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type,
|
||||||
else {
|
ExecutableElement factoryMethod, TypeElementMembers members) {
|
||||||
return resolveJavaBeanProperties(type, factoryMethod, members);
|
if (type.isConstructorBindingEnabled()) {
|
||||||
|
ExecutableElement constructor = type.getBindConstructor();
|
||||||
|
if (constructor != null) {
|
||||||
|
return resolveConstructorProperties(type.getType(), factoryMethod, members, constructor);
|
||||||
|
}
|
||||||
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
return resolveJavaBeanProperties(type.getType(), factoryMethod, members);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type, ExecutableElement factoryMethod,
|
Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type, ExecutableElement factoryMethod,
|
||||||
|
|
@ -108,12 +116,66 @@ class PropertyDescriptorResolver {
|
||||||
return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment);
|
return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutableElement resolveConstructor(TypeElement type) {
|
/**
|
||||||
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
|
* Wrapper around a {@link TypeElement} that could be bound.
|
||||||
if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0) {
|
*/
|
||||||
return constructors.get(0);
|
private static class ConfigurationPropertiesTypeElement {
|
||||||
|
|
||||||
|
private final TypeElement type;
|
||||||
|
|
||||||
|
private final boolean constructorBoundType;
|
||||||
|
|
||||||
|
private final List<ExecutableElement> constructors;
|
||||||
|
|
||||||
|
private final List<ExecutableElement> boundConstructors;
|
||||||
|
|
||||||
|
ConfigurationPropertiesTypeElement(TypeElement type, boolean constructorBoundType,
|
||||||
|
List<ExecutableElement> constructors, List<ExecutableElement> boundConstructors) {
|
||||||
|
this.type = type;
|
||||||
|
this.constructorBoundType = constructorBoundType;
|
||||||
|
this.constructors = constructors;
|
||||||
|
this.boundConstructors = boundConstructors;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
TypeElement getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isConstructorBindingEnabled() {
|
||||||
|
return this.constructorBoundType || !this.boundConstructors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutableElement getBindConstructor() {
|
||||||
|
if (this.constructorBoundType && this.boundConstructors.isEmpty()) {
|
||||||
|
return findBoundConstructor();
|
||||||
|
}
|
||||||
|
if (this.boundConstructors.size() == 1) {
|
||||||
|
return this.boundConstructors.get(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutableElement findBoundConstructor() {
|
||||||
|
ExecutableElement boundConstructor = null;
|
||||||
|
for (ExecutableElement canidate : this.constructors) {
|
||||||
|
if (!canidate.getParameters().isEmpty()) {
|
||||||
|
if (boundConstructor != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boundConstructor = canidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boundConstructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) {
|
||||||
|
boolean constructorBoundType = env.hasConstructorBindingAnnotation(type);
|
||||||
|
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
|
||||||
|
List<ExecutableElement> boundConstructors = constructors.stream()
|
||||||
|
.filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
|
||||||
|
return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
|
||||||
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||||
|
TestConfigurationMetadataAnnotationProcessor.CONSTRUCTOR_BINDING_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION);
|
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||||
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
||||||
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableClassConstructorBindingProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableMultiConstructorProperties;
|
||||||
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
|
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
|
||||||
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
|
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
|
||||||
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
|
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
|
||||||
|
|
@ -42,6 +44,8 @@ import org.springframework.boot.configurationsample.simple.HierarchicalPropertie
|
||||||
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
|
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
|
||||||
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
|
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
|
||||||
import org.springframework.boot.configurationsample.simple.SimpleProperties;
|
import org.springframework.boot.configurationsample.simple.SimpleProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.MatchingConstructorNoDirectiveProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.TwoConstructorsClassConstructorBindingExample;
|
||||||
import org.springframework.boot.configurationsample.specific.TwoConstructorsExample;
|
import org.springframework.boot.configurationsample.specific.TwoConstructorsExample;
|
||||||
import org.springframework.boot.testsupport.compiler.TestCompiler;
|
import org.springframework.boot.testsupport.compiler.TestCompiler;
|
||||||
|
|
||||||
|
|
@ -96,13 +100,45 @@ class PropertyDescriptorResolverTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void propertiesWithConstructorParameters() throws IOException {
|
void propertiesWithConstructorWithConstructorBinding() throws IOException {
|
||||||
process(ImmutableSimpleProperties.class, propertyNames(
|
process(ImmutableSimpleProperties.class, propertyNames(
|
||||||
(stream) -> assertThat(stream).containsExactly("theName", "flag", "comparator", "counter")));
|
(stream) -> assertThat(stream).containsExactly("theName", "flag", "comparator", "counter")));
|
||||||
|
process(ImmutableSimpleProperties.class, properties((stream) -> assertThat(stream)
|
||||||
|
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void propertiesWithSeveralConstructors() throws IOException {
|
void propertiesWithConstructorAndClassConstructorBinding() throws IOException {
|
||||||
|
process(ImmutableClassConstructorBindingProperties.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).containsExactly("name", "description")));
|
||||||
|
process(ImmutableClassConstructorBindingProperties.class, properties((stream) -> assertThat(stream)
|
||||||
|
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesWithConstructorAndClassConstructorBindingAndSeveralCandidates() throws IOException {
|
||||||
|
process(TwoConstructorsClassConstructorBindingExample.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).isEmpty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesWithConstructorNoDirective() throws IOException {
|
||||||
|
process(MatchingConstructorNoDirectiveProperties.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).containsExactly("name")));
|
||||||
|
process(MatchingConstructorNoDirectiveProperties.class, properties((stream) -> assertThat(stream)
|
||||||
|
.allMatch((predicate) -> predicate instanceof JavaBeanPropertyDescriptor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesWithMultiConstructor() throws IOException {
|
||||||
|
process(ImmutableMultiConstructorProperties.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).containsExactly("name", "description")));
|
||||||
|
process(ImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream)
|
||||||
|
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void propertiesWithMultiConstructorNoDirective() throws IOException {
|
||||||
process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name")));
|
process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name")));
|
||||||
process(TwoConstructorsExample.class,
|
process(TwoConstructorsExample.class,
|
||||||
properties((stream) -> assertThat(stream).element(0).isInstanceOf(JavaBeanPropertyDescriptor.class)));
|
properties((stream) -> assertThat(stream).element(0).isInstanceOf(JavaBeanPropertyDescriptor.class)));
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||||
|
|
||||||
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
|
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
|
||||||
|
|
||||||
|
public static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.configurationsample.ConstructorBinding";
|
||||||
|
|
||||||
public static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.configurationsample.DefaultValue";
|
public static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.configurationsample.DefaultValue";
|
||||||
|
|
||||||
public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint";
|
public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint";
|
||||||
|
|
@ -76,6 +78,11 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||||
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String constructorBindingAnnotation() {
|
||||||
|
return CONSTRUCTOR_BINDING_ANNOTATION;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String defaultValueAnnotation() {
|
protected String defaultValueAnnotation() {
|
||||||
return DEFAULT_VALUE_ANNOTATION;
|
return DEFAULT_VALUE_ANNOTATION;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to Spring Boot's {@code @ConstructorBinding} for testing (removes the need
|
||||||
|
* for a dependency on the real annotation).
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface ConstructorBinding {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
@ConstructorBinding
|
||||||
|
public @interface MetaConstructorBinding {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample.immutable;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.MetaConstructorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with several constructors.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@MetaConstructorBinding
|
||||||
|
public class ImmutableClassConstructorBindingProperties {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
public ImmutableClassConstructorBindingProperties(String name, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample.immutable;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.ConstructorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with several constructors.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ImmutableMultiConstructorProperties {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test description.
|
||||||
|
*/
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
public ImmutableMultiConstructorProperties(String name) {
|
||||||
|
this(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConstructorBinding
|
||||||
|
public ImmutableMultiConstructorProperties(String name, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.configurationsample.immutable;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.ConstructorBinding;
|
||||||
import org.springframework.boot.configurationsample.DefaultValue;
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,6 +48,7 @@ public class ImmutableSimpleProperties {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final Long counter;
|
private final Long counter;
|
||||||
|
|
||||||
|
@ConstructorBinding
|
||||||
public ImmutableSimpleProperties(@DefaultValue("boot") String theName, boolean flag, Comparator<?> comparator,
|
public ImmutableSimpleProperties(@DefaultValue("boot") String theName, boolean flag, Comparator<?> comparator,
|
||||||
Long counter) {
|
Long counter) {
|
||||||
this.theName = theName;
|
this.theName = theName;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.boot.configurationsample.specific;
|
package org.springframework.boot.configurationsample.specific;
|
||||||
|
|
||||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.ConstructorBinding;
|
||||||
import org.springframework.boot.configurationsample.DefaultValue;
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,6 +30,7 @@ public class InvalidDefaultValueCharacterProperties {
|
||||||
|
|
||||||
private final char letter;
|
private final char letter;
|
||||||
|
|
||||||
|
@ConstructorBinding
|
||||||
public InvalidDefaultValueCharacterProperties(@DefaultValue("bad") char letter) {
|
public InvalidDefaultValueCharacterProperties(@DefaultValue("bad") char letter) {
|
||||||
this.letter = letter;
|
this.letter = letter;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.boot.configurationsample.specific;
|
package org.springframework.boot.configurationsample.specific;
|
||||||
|
|
||||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.ConstructorBinding;
|
||||||
import org.springframework.boot.configurationsample.DefaultValue;
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,6 +27,7 @@ import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties("test")
|
@ConfigurationProperties("test")
|
||||||
|
@ConstructorBinding
|
||||||
public class InvalidDefaultValueFloatingPointProperties {
|
public class InvalidDefaultValueFloatingPointProperties {
|
||||||
|
|
||||||
private final Double ratio;
|
private final Double ratio;
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package org.springframework.boot.configurationsample.specific;
|
||||||
|
|
||||||
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
import org.springframework.boot.configurationsample.DefaultValue;
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
import org.springframework.boot.configurationsample.MetaConstructorBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Demonstrates that an invalid default number value leads to a compilation failure.
|
* Demonstrates that an invalid default number value leads to a compilation failure.
|
||||||
|
|
@ -25,6 +26,7 @@ import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties("test")
|
@ConfigurationProperties("test")
|
||||||
|
@MetaConstructorBinding
|
||||||
public class InvalidDefaultValueNumberProperties {
|
public class InvalidDefaultValueNumberProperties {
|
||||||
|
|
||||||
private final int counter;
|
private final int counter;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample.specific;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple properties with a constructor but no binding directive.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class MatchingConstructorNoDirectiveProperties {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public MatchingConstructorNoDirectiveProperties(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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.configurationsample.specific;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.MetaConstructorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type that declares constructor binding but with two available constructors.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@MetaConstructorBinding
|
||||||
|
public class TwoConstructorsClassConstructorBindingExample {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public TwoConstructorsClassConstructorBindingExample(String name) {
|
||||||
|
this(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoConstructorsClassConstructorBindingExample(String name, String description) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue