diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java index 563341654e1..b423301fbf4 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMapping.java @@ -275,6 +275,17 @@ final class AnnotationTypeMapping { return true; } + // Does an attribute value need to be resolved from a different annotation + // in the meta-annotation hierarchy? This can happen when an attribute is + // overridden via @AliasFor further up a multi-level annotation hierarchy + // whose root annotation does not redeclare the target attribute itself, + // in which case the override cannot be tracked via aliasMappings/aliasedBy. + for (int i = 0; i < this.annotationValueSource.length; i++) { + if (this.annotationValueSource[i] != null && this.annotationValueSource[i] != this) { + return true; + } + } + // Has nested annotations or arrays of annotations that are synthesizable? if (getAttributes().hasNestedAnnotation()) { AttributeMethods attributeMethods = getAttributes(); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java index bed8407fc9d..8ae3b01a5fc 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java @@ -513,6 +513,9 @@ public interface MergedAnnotation { * it has not already been synthesized and one of the following is true. * diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index f28f7760582..67d181818fe 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -354,8 +354,7 @@ final class TypeMappedAnnotation extends AbstractMergedAnn /** * Determine if the supplied annotation has not already been synthesized - * and whether the mapped annotation is a composed annotation - * that needs to have its attributes merged or the mapped annotation is + * and whether the mapped annotation is * {@linkplain AnnotationTypeMapping#isSynthesizable() synthesizable} in general. * @param annotation the annotation to check * @since 5.3.22 @@ -365,11 +364,6 @@ final class TypeMappedAnnotation extends AbstractMergedAnn if (AnnotationUtils.isSynthesizedAnnotation(annotation)) { return false; } - // Is this a mapped annotation for a composed annotation, and are there - // annotation attributes (mirrors) that need to be merged? - if (getDistance() > 0 && this.resolvedMirrors.length > 0) { - return true; - } // Is the mapped annotation itself synthesizable? return this.mapping.isSynthesizable(); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 3c3b534a0f9..4aa1adff507 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -1565,6 +1565,22 @@ class MergedAnnotationsTests { assertNotSynthesized(enableGlobalAuthentication); } + /** + * A meta-annotation that declares an attribute should not be synthesized + * merely because it is meta-present rather than directly present, as long + * as none of its attributes are mirrored or overridden anywhere in the + * annotation hierarchy. + */ + @Test + void synthesizeShouldNotSynthesizeMetaAnnotationsWithNonOverriddenAttributes() { + MergedAnnotations mergedAnnotations = MergedAnnotations.from(ComponentWithPlainAttributeMetaAnnotation.class); + + PlainAttributeMetaAnnotation plainAttributeMetaAnnotation = + mergedAnnotations.get(PlainAttributeMetaAnnotation.class).synthesize(); + assertThat(plainAttributeMetaAnnotation.value()).isEqualTo("enigma"); + assertNotSynthesized(plainAttributeMetaAnnotation); + } + /** * If an attempt is made to synthesize an annotation from an annotation instance * that has already been synthesized, the original synthesized annotation should @@ -3278,6 +3294,24 @@ class MergedAnnotationsTests { static class SecurityConfig { } + /** + * Meta-annotation that declares an attribute which is never mirrored or + * overridden anywhere in the annotation hierarchy. + */ + @Retention(RUNTIME) + @interface PlainAttributeMetaAnnotation { + String value() default "enigma"; + } + + @PlainAttributeMetaAnnotation + @Retention(RUNTIME) + @interface ComposedPlainAttributeAnnotation { + } + + @ComposedPlainAttributeAnnotation + static class ComponentWithPlainAttributeMetaAnnotation { + } + @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @interface RootAnnotation {