提交 42a36349 编写于 作者: S Sam Brannen

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
上级 8f0849f3
/*
* 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 {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册