Skip to content

Commit ae51ca9

Browse files
committed
Revert "Remove deprecated "enclosing classes" search strategy for MergedAnnotations"
This reverts commit 819d425. See gh-28080
1 parent 9764f0e commit ae51ca9

File tree

4 files changed

+93
-12
lines changed

4 files changed

+93
-12
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ private static <C, R> R processClass(C context, Class<?> source,
9999
return switch (searchStrategy) {
100100
case DIRECT -> processElement(context, source, processor);
101101
case INHERITED_ANNOTATIONS -> processClassInheritedAnnotations(context, source, searchStrategy, processor);
102-
case SUPERCLASS -> processClassHierarchy(context, source, processor, false);
103-
case TYPE_HIERARCHY -> processClassHierarchy(context, source, processor, true);
102+
case SUPERCLASS -> processClassHierarchy(context, source, processor, false, false);
103+
case TYPE_HIERARCHY -> processClassHierarchy(context, source, processor, true, false);
104+
case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES -> processClassHierarchy(context, source, processor, true, true);
104105
};
105106
}
106107

@@ -160,14 +161,15 @@ private static <C, R> R processClassInheritedAnnotations(C context, Class<?> sou
160161

161162
@Nullable
162163
private static <C, R> R processClassHierarchy(C context, Class<?> source,
163-
AnnotationsProcessor<C, R> processor, boolean includeInterfaces) {
164+
AnnotationsProcessor<C, R> processor, boolean includeInterfaces, boolean includeEnclosing) {
164165

165-
return processClassHierarchy(context, new int[] {0}, source, processor, includeInterfaces);
166+
return processClassHierarchy(context, new int[] {0}, source, processor,
167+
includeInterfaces, includeEnclosing);
166168
}
167169

168170
@Nullable
169171
private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex, Class<?> source,
170-
AnnotationsProcessor<C, R> processor, boolean includeInterfaces) {
172+
AnnotationsProcessor<C, R> processor, boolean includeInterfaces, boolean includeEnclosing) {
171173

172174
try {
173175
R result = processor.doWithAggregate(context, aggregateIndex[0]);
@@ -186,7 +188,7 @@ private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex, C
186188
if (includeInterfaces) {
187189
for (Class<?> interfaceType : source.getInterfaces()) {
188190
R interfacesResult = processClassHierarchy(context, aggregateIndex,
189-
interfaceType, processor, true);
191+
interfaceType, processor, true, includeEnclosing);
190192
if (interfacesResult != null) {
191193
return interfacesResult;
192194
}
@@ -195,11 +197,31 @@ private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex, C
195197
Class<?> superclass = source.getSuperclass();
196198
if (superclass != Object.class && superclass != null) {
197199
R superclassResult = processClassHierarchy(context, aggregateIndex,
198-
superclass, processor, includeInterfaces);
200+
superclass, processor, includeInterfaces, includeEnclosing);
199201
if (superclassResult != null) {
200202
return superclassResult;
201203
}
202204
}
205+
if (includeEnclosing) {
206+
// Since merely attempting to load the enclosing class may result in
207+
// automatic loading of sibling nested classes that in turn results
208+
// in an exception such as NoClassDefFoundError, we wrap the following
209+
// in its own dedicated try-catch block in order not to preemptively
210+
// halt the annotation scanning process.
211+
try {
212+
Class<?> enclosingClass = source.getEnclosingClass();
213+
if (enclosingClass != null) {
214+
R enclosingResult = processClassHierarchy(context, aggregateIndex,
215+
enclosingClass, processor, includeInterfaces, true);
216+
if (enclosingResult != null) {
217+
return enclosingResult;
218+
}
219+
}
220+
}
221+
catch (Throwable ex) {
222+
AnnotationUtils.handleIntrospectionFailure(source, ex);
223+
}
224+
}
203225
}
204226
catch (Throwable ex) {
205227
AnnotationUtils.handleIntrospectionFailure(source, ex);
@@ -216,7 +238,8 @@ private static <C, R> R processMethod(C context, Method source,
216238
case DIRECT, INHERITED_ANNOTATIONS -> processMethodInheritedAnnotations(context, source, processor);
217239
case SUPERCLASS -> processMethodHierarchy(context, new int[]{0}, source.getDeclaringClass(),
218240
processor, source, false);
219-
case TYPE_HIERARCHY -> processMethodHierarchy(context, new int[]{0}, source.getDeclaringClass(),
241+
case TYPE_HIERARCHY, TYPE_HIERARCHY_AND_ENCLOSING_CLASSES -> processMethodHierarchy(context, new int[]{0},
242+
source.getDeclaringClass(),
220243
processor, source, true);
221244
};
222245
}
@@ -483,8 +506,12 @@ private static boolean isWithoutHierarchy(AnnotatedElement source, SearchStrateg
483506
if (source == Object.class) {
484507
return true;
485508
}
486-
if (source instanceof Class<?> sourceClass) {
487-
return (sourceClass.getSuperclass() == Object.class && sourceClass.getInterfaces().length == 0);
509+
if (source instanceof Class) {
510+
Class<?> sourceClass = (Class<?>) source;
511+
boolean noSuperTypes = (sourceClass.getSuperclass() == Object.class &&
512+
sourceClass.getInterfaces().length == 0);
513+
return (searchStrategy == SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES ? noSuperTypes &&
514+
sourceClass.getEnclosingClass() == null : noSuperTypes);
488515
}
489516
if (source instanceof Method sourceMethod) {
490517
return (Modifier.isPrivate(sourceMethod.getModifiers()) ||

spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotations.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -472,8 +472,20 @@ enum SearchStrategy {
472472
* superclasses and implemented interfaces. Superclass annotations do
473473
* not need to be meta-annotated with {@link Inherited @Inherited}.
474474
*/
475-
TYPE_HIERARCHY
475+
TYPE_HIERARCHY,
476476

477+
/**
478+
* Perform a full search of the entire type hierarchy on the source
479+
* <em>and</em> any enclosing classes. This strategy is similar to
480+
* {@link #TYPE_HIERARCHY} except that {@linkplain Class#getEnclosingClass()
481+
* enclosing classes} are also searched. Superclass annotations do not
482+
* need to be meta-annotated with {@link Inherited @Inherited}. When
483+
* searching a {@link Method} source, this strategy is identical to
484+
* {@link #TYPE_HIERARCHY}.
485+
* @deprecated as of Spring Framework 5.3.17; to be removed in Spring Framework 6.0
486+
*/
487+
@Deprecated
488+
TYPE_HIERARCHY_AND_ENCLOSING_CLASSES
477489
}
478490

479491
}

spring-core/src/test/java/org/springframework/core/annotation/AnnotationsScannerTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,32 @@ void typeHierarchyStrategyOnMethodWithGenericParameterNonOverrideScansAnnotation
420420
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY)).containsExactly("0:TestAnnotation1");
421421
}
422422

423+
@Test
424+
@SuppressWarnings("deprecation")
425+
void typeHierarchyWithEnclosedStrategyOnEnclosedStaticClassScansAnnotations() {
426+
Class<?> source = AnnotationEnclosingClassSample.EnclosedStatic.EnclosedStaticStatic.class;
427+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES))
428+
.containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne");
429+
}
430+
431+
@Test
432+
@SuppressWarnings("deprecation")
433+
void typeHierarchyWithEnclosedStrategyOnEnclosedInnerClassScansAnnotations() {
434+
Class<?> source = AnnotationEnclosingClassSample.EnclosedInner.EnclosedInnerInner.class;
435+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES))
436+
.containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne");
437+
}
438+
439+
@Test
440+
@SuppressWarnings("deprecation")
441+
void typeHierarchyWithEnclosedStrategyOnMethodHierarchyUsesTypeHierarchyScan() {
442+
Method source = methodFrom(WithHierarchy.class);
443+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)).containsExactly(
444+
"0:TestAnnotation1", "1:TestAnnotation5", "1:TestInheritedAnnotation5",
445+
"2:TestAnnotation6", "3:TestAnnotation2", "3:TestInheritedAnnotation2",
446+
"4:TestAnnotation3", "5:TestAnnotation4");
447+
}
448+
423449
@Test
424450
void scanWhenProcessorReturnsFromDoWithAggregateExitsEarly() {
425451
String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class,

spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,22 @@ void streamTypeHierarchyFromClassWithInterface() throws Exception {
710710
Transactional.class)).hasSize(1);
711711
}
712712

713+
@Test
714+
@SuppressWarnings("deprecation")
715+
void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedInnerClassWithAnnotatedEnclosingClass() {
716+
Stream<Class<?>> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedInnerClass.class,
717+
SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType);
718+
assertThat(classes).containsExactly(Component.class, Indexed.class);
719+
}
720+
721+
@Test
722+
@SuppressWarnings("deprecation")
723+
void streamTypeHierarchyAndEnclosingClassesFromNonAnnotatedStaticNestedClassWithAnnotatedEnclosingClass() {
724+
Stream<Class<?>> classes = MergedAnnotations.from(AnnotatedClass.NonAnnotatedStaticNestedClass.class,
725+
SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES).stream().map(MergedAnnotation::getType);
726+
assertThat(classes).containsExactly(Component.class, Indexed.class);
727+
}
728+
713729
@Test
714730
void getFromMethodWithMethodAnnotationOnLeaf() throws Exception {
715731
Method method = Leaf.class.getMethod("annotatedOnLeaf");

0 commit comments

Comments
 (0)