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
This commit is contained in:
parent
8f0849f328
commit
42a36349e9
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
|
|
@ -240,52 +240,68 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Find a single {@link Annotation} of {@code annotationType} from the supplied
|
||||
* {@link Class}, traversing its annotations, interfaces, and superclasses if
|
||||
* no annotation can be found on the given class itself.
|
||||
* Find a single {@link Annotation} of {@code annotationType} on the
|
||||
* supplied {@link Class}, traversing its interfaces, annotations, and
|
||||
* superclasses if the annotation is not <em>present</em> on the given class
|
||||
* itself.
|
||||
* <p>This method explicitly handles class-level annotations which are not
|
||||
* declared as {@link java.lang.annotation.Inherited inherited} <i>as well
|
||||
* as meta-annotations and annotations on interfaces</i>.
|
||||
* declared as {@link java.lang.annotation.Inherited inherited} <em>as well
|
||||
* as meta-annotations and annotations on interfaces</em>.
|
||||
* <p>The algorithm operates as follows:
|
||||
* <ol>
|
||||
* <li>Search for an annotation on the given class and return it if found.
|
||||
* <li>Recursively search through all interfaces that the given class
|
||||
* declares, returning the annotation from the first matching candidate, if any.
|
||||
* <li>Recursively search through all annotations that the given class
|
||||
* declares, returning the annotation from the first matching candidate, if any.
|
||||
* <li>Proceed with introspection of the superclass hierarchy of the given
|
||||
* class by returning to step #1 with the superclass as the class to look for
|
||||
* annotations on.
|
||||
* <li>Search for the annotation on the given class and return it if found.
|
||||
* <li>Recursively search through all interfaces that the given class declares.
|
||||
* <li>Recursively search through all annotations that the given class declares.
|
||||
* <li>Recursively search through the superclass hierarchy of the given class.
|
||||
* </ol>
|
||||
* <p>Note: in this context, the term <em>recursively</em> means that the search
|
||||
* process continues by returning to step #1 with the current interface,
|
||||
* annotation, or superclass as the class to look for annotations on.
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param annotationType the annotation class to look for
|
||||
* @return the annotation found, or {@code null} if none found
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @return the annotation if found, or {@code null} if not found
|
||||
*/
|
||||
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
|
||||
return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm for {@link #findAnnotation(Class, Class)},
|
||||
* avoiding endless recursion by tracking which annotations have already
|
||||
* been visited.
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @param visitedAnnotations the set of annotations that have already been visited
|
||||
* @return the annotation if found, or {@code null} if not found
|
||||
*/
|
||||
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType,
|
||||
Set<Annotation> visitedAnnotations) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
|
||||
A annotation = clazz.getAnnotation(annotationType);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
for (Class<?> ifc : clazz.getInterfaces()) {
|
||||
annotation = findAnnotation(ifc, annotationType);
|
||||
annotation = findAnnotation(ifc, annotationType, visitedAnnotations);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
if (!Annotation.class.isAssignableFrom(clazz)) {
|
||||
for (Annotation ann : clazz.getAnnotations()) {
|
||||
annotation = findAnnotation(ann.annotationType(), annotationType);
|
||||
for (Annotation ann : clazz.getAnnotations()) {
|
||||
if (!visitedAnnotations.contains(ann)) {
|
||||
visitedAnnotations.add(ann);
|
||||
annotation = findAnnotation(ann.annotationType(), annotationType, visitedAnnotations);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null || superClass.equals(Object.class)) {
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass == null || superclass.equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
return findAnnotation(superClass, annotationType);
|
||||
return findAnnotation(superclass, annotationType, visitedAnnotations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -111,6 +111,33 @@ public class AnnotationUtilsTests {
|
|||
assertEquals("meta1", component.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationOnMetaMetaAnnotatedClass() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
|
||||
assertNotNull("Should find meta-annotation on composed annotation on class", component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationOnMetaMetaMetaAnnotatedClass() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
|
||||
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
|
||||
assertEquals("meta2", component.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
// TransactionalClass is NOT annotated or meta-annotated with @Component
|
||||
Component component = AnnotationUtils.findAnnotation(TransactionalClass.class, Component.class);
|
||||
assertNull("Should not find @Component on TransactionalClass", component);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
|
||||
Component component = AnnotationUtils.findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
|
||||
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAnnotationDeclaringClass() throws Exception {
|
||||
// no class-level annotation
|
||||
|
|
@ -335,6 +362,31 @@ public class AnnotationUtilsTests {
|
|||
@interface Meta2 {
|
||||
}
|
||||
|
||||
@Meta2
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaMeta {
|
||||
}
|
||||
|
||||
@MetaMeta
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaMetaMeta {
|
||||
}
|
||||
|
||||
@MetaCycle3
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle1 {
|
||||
}
|
||||
|
||||
@MetaCycle1
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle2 {
|
||||
}
|
||||
|
||||
@MetaCycle2
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MetaCycle3 {
|
||||
}
|
||||
|
||||
@Meta1
|
||||
static interface InterfaceWithMetaAnnotation {
|
||||
}
|
||||
|
|
@ -343,6 +395,17 @@ public class AnnotationUtilsTests {
|
|||
static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation {
|
||||
}
|
||||
|
||||
@MetaMeta
|
||||
static class MetaMetaAnnotatedClass {
|
||||
}
|
||||
|
||||
@MetaMetaMeta
|
||||
static class MetaMetaMetaAnnotatedClass {
|
||||
}
|
||||
|
||||
@MetaCycle3
|
||||
static class MetaCycleAnnotatedClass {
|
||||
}
|
||||
|
||||
public static interface AnnotatedInterface {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue