diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 4b00e2c5703..e0180ba8ce3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -78,7 +78,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.DeprecatedConfigurationProperty"; - static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.ConstructorBinding"; + static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.bind.ConstructorBinding"; static final String AUTOWIRED_ANNOTATION = "org.springframework.beans.factory.annotation.Autowired"; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java index 12e74e47596..b83d784e1c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java @@ -36,6 +36,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; @@ -184,7 +185,7 @@ class MetadataGenerationEnvironment { } boolean hasConstructorBindingAnnotation(ExecutableElement element) { - return hasAnnotation(element, this.constructorBindingAnnotation); + return hasAnnotation(element, this.constructorBindingAnnotation, true); } boolean hasAutowiredAnnotation(ExecutableElement element) { @@ -192,7 +193,40 @@ class MetadataGenerationEnvironment { } boolean hasAnnotation(Element element, String type) { - return getAnnotation(element, type) != null; + return hasAnnotation(element, type, false); + } + + boolean hasAnnotation(Element element, String type, boolean considerMetaAnnotations) { + if (element != null) { + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (type.equals(annotation.getAnnotationType().toString())) { + return true; + } + } + if (considerMetaAnnotations) { + Set seen = new HashSet<>(); + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (hasMetaAnnotation(annotation.getAnnotationType().asElement(), type, seen)) { + return true; + } + } + + } + } + return false; + } + + private boolean hasMetaAnnotation(Element annotationElement, String type, Set seen) { + if (seen.add(annotationElement)) { + for (AnnotationMirror annotation : annotationElement.getAnnotationMirrors()) { + DeclaredType annotationType = annotation.getAnnotationType(); + if (type.equals(annotationType.toString()) + || hasMetaAnnotation(annotationType.asElement(), type, seen)) { + return true; + } + } + } + return false; } AnnotationMirror getAnnotation(Element element, String type) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java index 4383b100248..977c20e0397 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; +import org.springframework.boot.configurationsample.immutable.DeprecatedImmutableMultiConstructorProperties; import org.springframework.boot.configurationsample.immutable.ImmutableClassConstructorBindingProperties; import org.springframework.boot.configurationsample.immutable.ImmutableDeducedConstructorBindingProperties; import org.springframework.boot.configurationsample.immutable.ImmutableMultiConstructorProperties; @@ -147,6 +148,16 @@ class PropertyDescriptorResolverTests { .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } + @Test + @Deprecated(since = "3.0.0", forRemoval = true) + @SuppressWarnings("removal") + void propertiesWithMultiConstructorAndDeprecatedAnnotation() throws IOException { + process(DeprecatedImmutableMultiConstructorProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); + process(DeprecatedImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream) + .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); + } + @Test void propertiesWithMultiConstructorNoDirective() throws IOException { process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name"))); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java index f9fc4d136cd..3d4d95dc2ae 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java @@ -28,7 +28,7 @@ import java.lang.annotation.Target; * * @author Stephane Nicoll */ -@Target(ElementType.CONSTRUCTOR) +@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConstructorBinding { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java new file mode 100644 index 00000000000..48a95646f70 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DeprecatedConstructorBinding.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.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 deprecated + * {@code @org.springframework.boot.context.properties.ConstructorBinding} for testing + * (removes the need for a dependency on the real annotation). + * + * @author Stephane Nicoll + */ +@Target(ElementType.CONSTRUCTOR) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConstructorBinding +@Deprecated(since = "3.0.0", forRemoval = true) +public @interface DeprecatedConstructorBinding { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java new file mode 100644 index 00000000000..d2e0305fc14 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/DeprecatedImmutableMultiConstructorProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationsample.immutable; + +/** + * Simple immutable properties with several constructors. + * + * @author Stephane Nicoll + */ +@SuppressWarnings("unused") +@Deprecated(since = "3.0.0", forRemoval = true) +public class DeprecatedImmutableMultiConstructorProperties { + + private final String name; + + /** + * Test description. + */ + private final String description; + + public DeprecatedImmutableMultiConstructorProperties(String name) { + this(name, null); + } + + @SuppressWarnings("removal") + @org.springframework.boot.configurationsample.DeprecatedConstructorBinding + public DeprecatedImmutableMultiConstructorProperties(String name, String description) { + this.name = name; + this.description = description; + } + +}