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:
Madhura Bhave
2021-12-10 09:45:48 -08:00
parent bc2c637d63
commit 44b88cc88c
44 changed files with 693 additions and 391 deletions

View File

@@ -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.
@@ -79,6 +79,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.ConstructorBinding";
static final String AUTOWIRED_ANNOTATION = "org.springframework.beans.factory.annotation.Autowired";
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.context.properties.bind.DefaultValue";
static final String CONTROLLER_ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint";
@@ -122,6 +124,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return CONSTRUCTOR_BINDING_ANNOTATION;
}
protected String autowiredAnnotation() {
return AUTOWIRED_ANNOTATION;
}
protected String defaultValueAnnotation() {
return DEFAULT_VALUE_ANNOTATION;
}
@@ -156,7 +162,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotations(),
constructorBindingAnnotation(), autowiredAnnotation(), defaultValueAnnotation(), endpointAnnotations(),
readOperationAnnotation(), nameAnnotation());
}

View File

@@ -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.
@@ -99,10 +99,12 @@ class MetadataGenerationEnvironment {
private final String nameAnnotation;
private final String autowiredAnnotation;
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
String constructorBindingAnnotation, String defaultValueAnnotation, Set<String> endpointAnnotations,
String readOperationAnnotation, String nameAnnotation) {
String constructorBindingAnnotation, String autowiredAnnotation, String defaultValueAnnotation,
Set<String> endpointAnnotations, String readOperationAnnotation, String nameAnnotation) {
this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils();
this.messager = environment.getMessager();
@@ -111,6 +113,7 @@ class MetadataGenerationEnvironment {
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
this.constructorBindingAnnotation = constructorBindingAnnotation;
this.autowiredAnnotation = autowiredAnnotation;
this.defaultValueAnnotation = defaultValueAnnotation;
this.endpointAnnotations = endpointAnnotations;
this.readOperationAnnotation = readOperationAnnotation;
@@ -180,14 +183,14 @@ class MetadataGenerationEnvironment {
return new ItemDeprecation(reason, replacement);
}
boolean hasConstructorBindingAnnotation(TypeElement typeElement) {
return hasAnnotationRecursive(typeElement, this.constructorBindingAnnotation);
}
boolean hasConstructorBindingAnnotation(ExecutableElement element) {
return hasAnnotation(element, this.constructorBindingAnnotation);
}
boolean hasAutowiredAnnotation(ExecutableElement element) {
return hasAnnotation(element, this.autowiredAnnotation);
}
boolean hasAnnotation(Element element, String type) {
return getAnnotation(element, type) != null;
}

View File

@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.boot.configurationprocessor;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -191,16 +192,29 @@ class PropertyDescriptorResolver {
static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) {
boolean constructorBoundType = isConstructorBoundType(type, env);
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
List<ExecutableElement> boundConstructors = constructors.stream()
.filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
List<ExecutableElement> boundConstructors = getBoundConstructors(env, constructors);
return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors);
}
private static boolean isConstructorBoundType(TypeElement type, MetadataGenerationEnvironment env) {
if (env.hasConstructorBindingAnnotation(type)
|| "java.lang.Record".equals(type.getSuperclass().toString())) {
return true;
private static List<ExecutableElement> getBoundConstructors(MetadataGenerationEnvironment env,
List<ExecutableElement> constructors) {
ExecutableElement bindConstructor = deduceBindConstructor(constructors, env);
if (bindConstructor != null) {
return Collections.singletonList(bindConstructor);
}
return constructors.stream().filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
}
private static ExecutableElement deduceBindConstructor(List<ExecutableElement> constructors,
MetadataGenerationEnvironment env) {
if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0
&& !env.hasAutowiredAnnotation(constructors.get(0))) {
return constructors.get(0);
}
return null;
}
private static boolean isConstructorBoundType(TypeElement type, MetadataGenerationEnvironment env) {
if (type.getNestingKind() == NestingKind.MEMBER) {
return isConstructorBoundType((TypeElement) type.getEnclosingElement(), env);
}