diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index ea41cc80b5..f27e81ac80 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -374,6 +374,7 @@ public abstract class AnnotationUtils { RepeatableContainers repeatableContainers = (containerAnnotationType != null ? RepeatableContainers.of(annotationType, containerAnnotationType) : RepeatableContainers.standardRepeatables()); + return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPERCLASS, repeatableContainers) .stream(annotationType) .filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex)) @@ -455,6 +456,7 @@ public abstract class AnnotationUtils { RepeatableContainers repeatableContainers = containerAnnotationType != null ? RepeatableContainers.of(annotationType, containerAnnotationType) : RepeatableContainers.standardRepeatables(); + return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, repeatableContainers) .stream(annotationType) .map(MergedAnnotation::withNonMergedAttributes) @@ -484,11 +486,13 @@ public abstract class AnnotationUtils { if (annotationType == null) { return null; } + // Shortcut: directly present on the element, with no merging needed? if (AnnotationFilter.PLAIN.matches(annotationType) || AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) { return annotatedElement.getDeclaredAnnotation(annotationType); } + // Exhaustive retrieval of merged annotations... return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none()) .get(annotationType).withNonMergedAttributes() @@ -515,11 +519,13 @@ public abstract class AnnotationUtils { if (annotationType == null) { return null; } + // Shortcut: directly present on the element, with no merging needed? if (AnnotationFilter.PLAIN.matches(annotationType) || AnnotationsScanner.hasPlainJavaAnnotationsOnly(method)) { return method.getDeclaredAnnotation(annotationType); } + // Exhaustive retrieval of merged annotations... return MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()) .get(annotationType).withNonMergedAttributes() @@ -553,11 +559,23 @@ public abstract class AnnotationUtils { if (annotationType == null) { return null; } + // Shortcut: directly present on the element, with no merging needed? if (AnnotationFilter.PLAIN.matches(annotationType) || AnnotationsScanner.hasPlainJavaAnnotationsOnly(clazz)) { - return clazz.getDeclaredAnnotation(annotationType); + A annotation = clazz.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + // For backwards compatibility, perform a superclass search with plain annotations + // even if not marked as @Inherited: e.g. a findAnnotation search for @Deprecated + Class superclass = clazz.getSuperclass(); + if (superclass == null || superclass == Object.class) { + return null; + } + return findAnnotation(superclass, annotationType); } + // Exhaustive retrieval of merged annotations... return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()) .get(annotationType).withNonMergedAttributes() diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index f0edf9c474..63c46631bd 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -946,24 +946,24 @@ class AnnotationUtilsTests { assertThat(synthesizedComponent.value()).as("value from synthesized component: ").isEqualTo("webController"); } - @Test // gh-22702 - void findAnnotationWithRepeatablesElements() { + @Test // gh-22702 + void findAnnotationWithRepeatablesElements() throws Exception { assertThat(AnnotationUtils.findAnnotation(TestRepeatablesClass.class, TestRepeatable.class)).isNull(); assertThat(AnnotationUtils.findAnnotation(TestRepeatablesClass.class, TestRepeatableContainer.class)).isNotNull(); } - @Test // gh-23856 - void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotations() { + @Test // gh-23856 + void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotations() throws Exception { MyRepeatableContainer annotation = AnnotationUtils.findAnnotation(MyRepeatableMeta1And2.class, MyRepeatableContainer.class); assertThat(annotation).isNotNull(); assertThat(annotation.value()).extracting(MyRepeatable::value).containsExactly("meta1", "meta2"); } - @Test // gh-23856 - void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotationsOnMethod() throws NoSuchMethodException { + @Test // gh-23856 + void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotationsOnMethod() throws Exception { Method method = getClass().getDeclaredMethod("methodWithComposedAnnotationMetaAnnotatedWithRepeatableAnnotations"); MyRepeatableContainer annotation = AnnotationUtils.findAnnotation(method, MyRepeatableContainer.class); @@ -971,6 +971,14 @@ class AnnotationUtilsTests { assertThat(annotation.value()).extracting(MyRepeatable::value).containsExactly("meta1", "meta2"); } + @Test // gh-23929 + void findDeprecatedAnnotation() throws Exception { + assertThat(getAnnotation(DeprecatedClass.class, Deprecated.class)).isNotNull(); + assertThat(getAnnotation(SubclassOfDeprecatedClass.class, Deprecated.class)).isNull(); + assertThat(findAnnotation(DeprecatedClass.class, Deprecated.class)).isNotNull(); + assertThat(findAnnotation(SubclassOfDeprecatedClass.class, Deprecated.class)).isNotNull(); + } + @SafeVarargs static T[] asArray(T... arr) { @@ -1810,4 +1818,12 @@ class AnnotationUtilsTests { @TestRepeatable("b") static class TestRepeatablesClass { } + + @Deprecated + static class DeprecatedClass { + } + + static class SubclassOfDeprecatedClass extends DeprecatedClass { + } + }