Skip to content

Commit 42a3634

Browse files
committed
Support nested meta-annotations in AnnotationUtils
Prior to this commit, AnnotationUtils.findAnnotation(Class, Class) claimed to recursively search through annotations; however, only one level of annotations was supported by the algorithm. This commit alters the search algorithm so that nested meta-annotations (i.e., meta-annotations on meta-annotations) are also supported. Issue: SPR-11448
1 parent 8f0849f commit 42a3634

File tree

2 files changed

+102
-23
lines changed

2 files changed

+102
-23
lines changed

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

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -240,52 +240,68 @@ private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
240240
}
241241

242242
/**
243-
* Find a single {@link Annotation} of {@code annotationType} from the supplied
244-
* {@link Class}, traversing its annotations, interfaces, and superclasses if
245-
* no annotation can be found on the given class itself.
243+
* Find a single {@link Annotation} of {@code annotationType} on the
244+
* supplied {@link Class}, traversing its interfaces, annotations, and
245+
* superclasses if the annotation is not <em>present</em> on the given class
246+
* itself.
246247
* <p>This method explicitly handles class-level annotations which are not
247-
* declared as {@link java.lang.annotation.Inherited inherited} <i>as well
248-
* as meta-annotations and annotations on interfaces</i>.
248+
* declared as {@link java.lang.annotation.Inherited inherited} <em>as well
249+
* as meta-annotations and annotations on interfaces</em>.
249250
* <p>The algorithm operates as follows:
250251
* <ol>
251-
* <li>Search for an annotation on the given class and return it if found.
252-
* <li>Recursively search through all interfaces that the given class
253-
* declares, returning the annotation from the first matching candidate, if any.
254-
* <li>Recursively search through all annotations that the given class
255-
* declares, returning the annotation from the first matching candidate, if any.
256-
* <li>Proceed with introspection of the superclass hierarchy of the given
257-
* class by returning to step #1 with the superclass as the class to look for
258-
* annotations on.
252+
* <li>Search for the annotation on the given class and return it if found.
253+
* <li>Recursively search through all interfaces that the given class declares.
254+
* <li>Recursively search through all annotations that the given class declares.
255+
* <li>Recursively search through the superclass hierarchy of the given class.
259256
* </ol>
257+
* <p>Note: in this context, the term <em>recursively</em> means that the search
258+
* process continues by returning to step #1 with the current interface,
259+
* annotation, or superclass as the class to look for annotations on.
260260
* @param clazz the class to look for annotations on
261-
* @param annotationType the annotation class to look for
262-
* @return the annotation found, or {@code null} if none found
261+
* @param annotationType the type of annotation to look for
262+
* @return the annotation if found, or {@code null} if not found
263263
*/
264264
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
265+
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
266+
}
267+
268+
/**
269+
* Perform the search algorithm for {@link #findAnnotation(Class, Class)},
270+
* avoiding endless recursion by tracking which annotations have already
271+
* been visited.
272+
* @param clazz the class to look for annotations on
273+
* @param annotationType the type of annotation to look for
274+
* @param visitedAnnotations the set of annotations that have already been visited
275+
* @return the annotation if found, or {@code null} if not found
276+
*/
277+
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType,
278+
Set<Annotation> visitedAnnotations) {
265279
Assert.notNull(clazz, "Class must not be null");
280+
266281
A annotation = clazz.getAnnotation(annotationType);
267282
if (annotation != null) {
268283
return annotation;
269284
}
270285
for (Class<?> ifc : clazz.getInterfaces()) {
271-
annotation = findAnnotation(ifc, annotationType);
286+
annotation = findAnnotation(ifc, annotationType, visitedAnnotations);
272287
if (annotation != null) {
273288
return annotation;
274289
}
275290
}
276-
if (!Annotation.class.isAssignableFrom(clazz)) {
277-
for (Annotation ann : clazz.getAnnotations()) {
278-
annotation = findAnnotation(ann.annotationType(), annotationType);
291+
for (Annotation ann : clazz.getAnnotations()) {
292+
if (!visitedAnnotations.contains(ann)) {
293+
visitedAnnotations.add(ann);
294+
annotation = findAnnotation(ann.annotationType(), annotationType, visitedAnnotations);
279295
if (annotation != null) {
280296
return annotation;
281297
}
282298
}
283299
}
284-
Class<?> superClass = clazz.getSuperclass();
285-
if (superClass == null || superClass.equals(Object.class)) {
300+
Class<?> superclass = clazz.getSuperclass();
301+
if (superclass == null || superclass.equals(Object.class)) {
286302
return null;
287303
}
288-
return findAnnotation(superClass, annotationType);
304+
return findAnnotation(superclass, annotationType, visitedAnnotations);
289305
}
290306

291307
/**

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,33 @@ public void findAnnotationPrefersInterfacesOverLocalMetaAnnotations() {
111111
assertEquals("meta1", component.value());
112112
}
113113

114+
@Test
115+
public void findAnnotationOnMetaMetaAnnotatedClass() {
116+
Component component = AnnotationUtils.findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
117+
assertNotNull("Should find meta-annotation on composed annotation on class", component);
118+
assertEquals("meta2", component.value());
119+
}
120+
121+
@Test
122+
public void findAnnotationOnMetaMetaMetaAnnotatedClass() {
123+
Component component = AnnotationUtils.findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
124+
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
125+
assertEquals("meta2", component.value());
126+
}
127+
128+
@Test
129+
public void findAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
130+
// TransactionalClass is NOT annotated or meta-annotated with @Component
131+
Component component = AnnotationUtils.findAnnotation(TransactionalClass.class, Component.class);
132+
assertNull("Should not find @Component on TransactionalClass", component);
133+
}
134+
135+
@Test
136+
public void findAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
137+
Component component = AnnotationUtils.findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
138+
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
139+
}
140+
114141
@Test
115142
public void testFindAnnotationDeclaringClass() throws Exception {
116143
// no class-level annotation
@@ -335,6 +362,31 @@ public void getRepeatableFromMethod() throws Exception {
335362
@interface Meta2 {
336363
}
337364

365+
@Meta2
366+
@Retention(RetentionPolicy.RUNTIME)
367+
@interface MetaMeta {
368+
}
369+
370+
@MetaMeta
371+
@Retention(RetentionPolicy.RUNTIME)
372+
@interface MetaMetaMeta {
373+
}
374+
375+
@MetaCycle3
376+
@Retention(RetentionPolicy.RUNTIME)
377+
@interface MetaCycle1 {
378+
}
379+
380+
@MetaCycle1
381+
@Retention(RetentionPolicy.RUNTIME)
382+
@interface MetaCycle2 {
383+
}
384+
385+
@MetaCycle2
386+
@Retention(RetentionPolicy.RUNTIME)
387+
@interface MetaCycle3 {
388+
}
389+
338390
@Meta1
339391
static interface InterfaceWithMetaAnnotation {
340392
}
@@ -343,6 +395,17 @@ static interface InterfaceWithMetaAnnotation {
343395
static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation {
344396
}
345397

398+
@MetaMeta
399+
static class MetaMetaAnnotatedClass {
400+
}
401+
402+
@MetaMetaMeta
403+
static class MetaMetaMetaAnnotatedClass {
404+
}
405+
406+
@MetaCycle3
407+
static class MetaCycleAnnotatedClass {
408+
}
346409

347410
public static interface AnnotatedInterface {
348411

0 commit comments

Comments
 (0)