Skip to content

Commit 17518ec

Browse files
committed
Add TYPE_HIERARCHY_AND_ENCLOSING_CLASSES strategy
Add a `TYPE_HIERARCHY_AND_ENCLOSING_CLASSES` annotation search strategy that can be used to search the full type hierarchy as well as any enclosing classes. Closes gh-23378
1 parent a6021cc commit 17518ec

File tree

4 files changed

+131
-7
lines changed

4 files changed

+131
-7
lines changed

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,12 @@ private static <C, R> R processClass(C context, Class<?> source,
123123
case INHERITED_ANNOTATIONS:
124124
return processClassInheritedAnnotations(context, source, processor, classFilter);
125125
case SUPERCLASS:
126-
return processClassHierarchy(context, new int[] {0}, source, processor, classFilter, false);
126+
return processClassHierarchy(context, source, processor, classFilter, false, false);
127127
case EXHAUSTIVE:
128128
case TYPE_HIERARCHY:
129-
return processClassHierarchy(context, new int[] {0}, source, processor, classFilter, true);
129+
return processClassHierarchy(context, source, processor, classFilter, true, false);
130+
case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:
131+
return processClassHierarchy(context, source, processor, classFilter, true, true);
130132
}
131133
throw new IllegalStateException("Unsupported search strategy " + searchStrategy);
132134
}
@@ -184,10 +186,22 @@ private static <C, R> R processClassInheritedAnnotations(C context, Class<?> sou
184186
return null;
185187
}
186188

189+
@Nullable
190+
private static <C, R> R processClassHierarchy(C context, Class<?> source,
191+
AnnotationsProcessor<C, R> processor,
192+
@Nullable BiPredicate<C, Class<?>> classFilter, boolean includeInterfaces,
193+
boolean includeEnclosing) {
194+
195+
int[] aggregateIndex = new int[] { 0 };
196+
return processClassHierarchy(context, aggregateIndex, source, processor,
197+
classFilter, includeInterfaces, includeEnclosing);
198+
}
199+
187200
@Nullable
188201
private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex,
189202
Class<?> source, AnnotationsProcessor<C, R> processor,
190-
@Nullable BiPredicate<C, Class<?>> classFilter, boolean includeInterfaces) {
203+
@Nullable BiPredicate<C, Class<?>> classFilter, boolean includeInterfaces,
204+
boolean includeEnclosing) {
191205

192206
R result = processor.doWithAggregate(context, aggregateIndex[0]);
193207
if (result != null) {
@@ -205,7 +219,7 @@ private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex,
205219
if (includeInterfaces) {
206220
for (Class<?> interfaceType : source.getInterfaces()) {
207221
R interfacesResult = processClassHierarchy(context, aggregateIndex,
208-
interfaceType, processor, classFilter, true);
222+
interfaceType, processor, classFilter, true, includeEnclosing);
209223
if (interfacesResult != null) {
210224
return interfacesResult;
211225
}
@@ -214,11 +228,21 @@ private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex,
214228
Class<?> superclass = source.getSuperclass();
215229
if (superclass != Object.class && superclass != null) {
216230
R superclassResult = processClassHierarchy(context, aggregateIndex,
217-
superclass, processor, classFilter, includeInterfaces);
231+
superclass, processor, classFilter, includeInterfaces,
232+
includeEnclosing);
218233
if (superclassResult != null) {
219234
return superclassResult;
220235
}
221236
}
237+
Class<?> enclosingClass = source.getEnclosingClass();
238+
if (includeEnclosing && enclosingClass != null) {
239+
R enclosingResult = processClassHierarchy(context, aggregateIndex,
240+
enclosingClass, processor, classFilter, includeInterfaces,
241+
includeEnclosing);
242+
if (enclosingResult != null) {
243+
return enclosingResult;
244+
}
245+
}
222246
return null;
223247
}
224248

@@ -237,6 +261,7 @@ private static <C, R> R processMethod(C context, Method source,
237261
processor, classFilter, source, false);
238262
case EXHAUSTIVE:
239263
case TYPE_HIERARCHY:
264+
case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:
240265
return processMethodHierarchy(context, new int[] {0}, source.getDeclaringClass(),
241266
processor, classFilter, source, true);
242267
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,22 @@ enum SearchStrategy {
434434
EXHAUSTIVE,
435435

436436
/**
437-
* Perform a full search of the entire type hierarchy , including
437+
* Perform a full search of the entire type hierarchy, including
438438
* superclasses and implemented interfaces. Superclass annotations do
439439
* not need to be meta-annotated with {@link Inherited @Inherited}.
440440
*/
441-
TYPE_HIERARCHY
441+
TYPE_HIERARCHY,
442442

443+
/**
444+
* Perform a full search of the entire type hierarchy on the source
445+
* <em>and</em> any enclosing classes. This strategy is similar to
446+
* {@link #TYPE_HIERARCHY} except that {@link Class#getEnclosingClass()
447+
* enclosing classes} are also searched. Superclass annotations do not
448+
* need to be meta-annotated with {@link Inherited @Inherited}. When
449+
* searching a {@link Method} source, this strategy is identical to
450+
* {@link #TYPE_HIERARCHY}.
451+
*/
452+
TYPE_HIERARCHY_AND_ENCLOSING_CLASSES
443453
}
444454

445455
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
/**
23+
* Example class used to test {@link AnnotationsScanner} with enclosing classes.
24+
*
25+
* @author Phillip Webb
26+
* @since 5.2
27+
*/
28+
@AnnotationEnclosingClassSample.EnclosedOne
29+
public class AnnotationEnclosingClassSample {
30+
31+
@EnclosedTwo
32+
public static class EnclosedStatic {
33+
34+
@EnclosedThree
35+
public static class EnclosedStaticStatic {
36+
37+
}
38+
39+
}
40+
41+
@EnclosedTwo
42+
public class EnclosedInner {
43+
44+
@EnclosedThree
45+
public class EnclosedInnerInner {
46+
47+
}
48+
49+
}
50+
51+
@Retention(RetentionPolicy.RUNTIME)
52+
public static @interface EnclosedOne {
53+
54+
}
55+
56+
@Retention(RetentionPolicy.RUNTIME)
57+
public static @interface EnclosedTwo {
58+
59+
}
60+
61+
@Retention(RetentionPolicy.RUNTIME)
62+
public static @interface EnclosedThree {
63+
64+
}
65+
66+
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,29 @@ public void typeHierarchyStrategyOnMethodWithGenericParameterNonOverrideScansAnn
454454
"0:TestAnnotation1");
455455
}
456456

457+
@Test
458+
public void typeHierarchyWithEnclosedStrategyOnEnclosedStaticClassScansAnnotations() {
459+
Class<?> source = AnnotationEnclosingClassSample.EnclosedStatic.EnclosedStaticStatic.class;
460+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES))
461+
.containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne");
462+
}
463+
464+
@Test
465+
public void typeHierarchyWithEnclosedStrategyOnEnclosedInnerClassScansAnnotations() {
466+
Class<?> source = AnnotationEnclosingClassSample.EnclosedInner.EnclosedInnerInner.class;
467+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES))
468+
.containsExactly("0:EnclosedThree", "1:EnclosedTwo", "2:EnclosedOne");
469+
}
470+
471+
@Test
472+
public void typeHierarchyWithEnclosedStrategyOnMethodHierarchyUsesTypeHierarchyScan() {
473+
Method source = methodFrom(WithHierarchy.class);
474+
assertThat(scan(source, SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES)).containsExactly(
475+
"0:TestAnnotation1", "1:TestAnnotation5", "1:TestInheritedAnnotation5",
476+
"2:TestAnnotation6", "3:TestAnnotation2", "3:TestInheritedAnnotation2",
477+
"4:TestAnnotation3", "5:TestAnnotation4");
478+
}
479+
457480
@Test
458481
public void scanWhenProcessorReturnsFromDoWithAggregateExitsEarly() {
459482
String result = AnnotationsScanner.scan(this, WithSingleSuperclass.class,

0 commit comments

Comments
 (0)