diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java index 4b8a86af80..fa90a703b4 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java @@ -99,8 +99,9 @@ abstract class AnnotationsScanner { return switch (searchStrategy) { case DIRECT -> processElement(context, source, processor); case INHERITED_ANNOTATIONS -> processClassInheritedAnnotations(context, source, searchStrategy, processor); - case SUPERCLASS -> processClassHierarchy(context, source, processor, false); - case TYPE_HIERARCHY -> processClassHierarchy(context, source, processor, true); + case SUPERCLASS -> processClassHierarchy(context, source, processor, false, false); + case TYPE_HIERARCHY -> processClassHierarchy(context, source, processor, true, false); + case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES -> processClassHierarchy(context, source, processor, true, true); }; } @@ -160,14 +161,15 @@ abstract class AnnotationsScanner { @Nullable private static R processClassHierarchy(C context, Class source, - AnnotationsProcessor processor, boolean includeInterfaces) { + AnnotationsProcessor processor, boolean includeInterfaces, boolean includeEnclosing) { - return processClassHierarchy(context, new int[] {0}, source, processor, includeInterfaces); + return processClassHierarchy(context, new int[] {0}, source, processor, + includeInterfaces, includeEnclosing); } @Nullable private static R processClassHierarchy(C context, int[] aggregateIndex, Class source, - AnnotationsProcessor processor, boolean includeInterfaces) { + AnnotationsProcessor processor, boolean includeInterfaces, boolean includeEnclosing) { try { R result = processor.doWithAggregate(context, aggregateIndex[0]); @@ -186,7 +188,7 @@ abstract class AnnotationsScanner { if (includeInterfaces) { for (Class interfaceType : source.getInterfaces()) { R interfacesResult = processClassHierarchy(context, aggregateIndex, - interfaceType, processor, true); + interfaceType, processor, true, includeEnclosing); if (interfacesResult != null) { return interfacesResult; } @@ -195,11 +197,31 @@ abstract class AnnotationsScanner { Class superclass = source.getSuperclass(); if (superclass != Object.class && superclass != null) { R superclassResult = processClassHierarchy(context, aggregateIndex, - superclass, processor, includeInterfaces); + superclass, processor, includeInterfaces, includeEnclosing); if (superclassResult != null) { return superclassResult; } } + if (includeEnclosing) { + // Since merely attempting to load the enclosing class may result in + // automatic loading of sibling nested classes that in turn results + // in an exception such as NoClassDefFoundError, we wrap the following + // in its own dedicated try-catch block in order not to preemptively + // halt the annotation scanning process. + try { + Class enclosingClass = source.getEnclosingClass(); + if (enclosingClass != null) { + R enclosingResult = processClassHierarchy(context, aggregateIndex, + enclosingClass, processor, includeInterfaces, true); + if (enclosingResult != null) { + return enclosingResult; + } + } + } + catch (Throwable ex) { + AnnotationUtils.handleIntrospectionFailure(source, ex); + } + } } catch (Throwable ex) { AnnotationUtils.handleIntrospectionFailure(source, ex); @@ -216,7 +238,8 @@ abstract class AnnotationsScanner { case DIRECT, INHERITED_ANNOTATIONS -> processMethodInheritedAnnotations(context, source, processor); case SUPERCLASS -> processMethodHierarchy(context, new int[]{0}, source.getDeclaringClass(), processor, source, false); - case TYPE_HIERARCHY -> processMethodHierarchy(context, new int[]{0}, source.getDeclaringClass(), + case TYPE_HIERARCHY, TYPE_HIERARCHY_AND_ENCLOSING_CLASSES -> processMethodHierarchy(context, new int[]{0}, + source.getDeclaringClass(), processor, source, true); }; } @@ -483,8 +506,12 @@ abstract class AnnotationsScanner { if (source == Object.class) { return true; } - if (source instanceof Class sourceClass) { - return (sourceClass.getSuperclass() == Object.class && sourceClass.getInterfaces().length == 0); + if (source instanceof Class) { + Class sourceClass = (Class) source; + boolean noSuperTypes = (sourceClass.getSuperclass() == Object.class && + sourceClass.getInterfaces().length == 0); + return (searchStrategy == SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES ? noSuperTypes && + sourceClass.getEnclosingClass() == null : noSuperTypes); } if (source instanceof Method sourceMethod) { return (Modifier.isPrivate(sourceMethod.getModifiers()) || diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java index a488d29e87..28f7cf009a 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2020 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. @@ -472,8 +472,20 @@ public interface MergedAnnotations extends Iterable * superclasses and implemented interfaces. Superclass annotations do * not need to be meta-annotated with {@link Inherited @Inherited}. */ - TYPE_HIERARCHY + TYPE_HIERARCHY, + /** + * Perform a full search of the entire type hierarchy on the source + * and any enclosing classes. This strategy is similar to + * {@link #TYPE_HIERARCHY} except that {@linkplain Class#getEnclosingClass() + * enclosing classes} are also searched. Superclass annotations do not + * need to be meta-annotated with {@link Inherited @Inherited}. When + * searching a {@link Method} source, this strategy is identical to + * {@link #TYPE_HIERARCHY}. + * @deprecated as of Spring Framework 5.3.17; to be removed in Spring Framework 6.0 + */ + @Deprecated + TYPE_HIERARCHY_AND_ENCLOSING_CLASSES } } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java index dbbb4f00ce..e848090b12 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java @@ -420,6 +420,32 @@ class AnnotationsScannerTests { assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("0:TestAnnotation1"); } + @Test + @SuppressWarnings("deprecation") + void typeHierarchyWithEnclosedStrategyOnEnclosedStaticClassScansAnnotations() { + Class source = AnnotationEnclosingClassSample.EnclosedStatic.EnclosedStaticStatic.class; + assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)) + .containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne"); + } + + @Test + @SuppressWarnings("deprecation") + void typeHierarchyWithEnclosedStrategyOnEnclosedInnerClassScansAnnotations() { + Class source = AnnotationEnclosingClassSample.EnclosedInner.EnclosedInnerInner.class; + assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)) + .containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne"); + } + + @Test + @SuppressWarnings("deprecation") + void typeHierarchyWithEnclosedStrategyOnMethodHierarchyUsesTypeHierarchyScan() { + Method source = methodFrom(WithHierarchy.class); + assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)).containsExactly( + "0:TestAnnotation1", "1:TestAnnotation5", "1:TestInheritedAnnotation5", + "2:TestAnnotation6", "3:TestAnnotation2", "3:TestInheritedAnnotation2", + "4:TestAnnotation3", "5:TestAnnotation4"); + } + @Test void scanWhenProcessorReturnsFromDoWithAggregateExitsEarly() { String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class, 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 5c754da93e..53e82bf4a6 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 @@ -710,6 +710,22 @@ class MergedAnnotationsTests { Transactional.class)).hasSize(1); } + @Test + @SuppressWarnings("deprecation") + void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedInnerClassWithAnnotatedEnclosingClass() { + Stream> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedInnerClass.class, + SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType); + assertThat(classes).containsExactly(Component.class, Indexed.class); + } + + @Test + @SuppressWarnings("deprecation") + void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedStaticNestedClassWithAnnotatedEnclosingClass() { + Stream> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedStaticNestedClass.class, + SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType); + assertThat(classes).containsExactly(Component.class, Indexed.class); + } + @Test void getFromMethodWithMethodAnnotationOnLeaf() throws Exception { Method method = Leaf.class.getMethod("annotatedOnLeaf");