Support n meta-annotation levels on methods in AnnotationUtils

Prior to this commit, the search algorithm used by the
findAnnotation(Method, Class) method in AnnotationUtils only found
direct annotations or direct meta-annotations (i.e., one level of
meta-annotations).

This commit reworks the search algorithm so that it supports arbitrary
levels of meta-annotations on methods. To make this possible, a new
findAnnotation(AnnotatedElement, Class) method has been introduced in
AnnotationUtils.

This fix also allows for the @Ignore'd tests in
TransactionalEventListenerTests to be re-enabled.

Issue: SPR-12941
This commit is contained in:
Sam Brannen
2015-04-22 02:26:24 +02:00
parent 666d1cecc8
commit b9b0b78fa1
3 changed files with 140 additions and 29 deletions

View File

@@ -91,8 +91,9 @@ public abstract class AnnotationUtils {
* Get a single {@link Annotation} of {@code annotationType} from the supplied
* annotation: either the given annotation itself or a direct meta-annotation
* thereof.
* <p>Note that this method does <em>not</em> support arbitrary levels of
* meta-annotations.
* <p>Note that this method supports only a single level of meta-annotations.
* For support for arbitrary levels of meta-annotations, use one of the
* {@code find*()} methods instead.
* @param ann the Annotation to check
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @return the matching annotation, or {@code null} if not found
@@ -115,9 +116,11 @@ public abstract class AnnotationUtils {
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied
* {@link AnnotatedElement}.
* <p>Meta-annotations will be searched if the annotation is not
* <em>directly present</em> on the supplied element.
* {@link AnnotatedElement}, where the {@code AnnotatedElement} is either
* directly annotated or meta-annotated with the {@code annotationType}.
* <p>Note that this method supports only a single level of meta-annotations.
* For support for arbitrary levels of meta-annotations, use
* {@link #findAnnotation(AnnotatedElement, Class)} instead.
* @param annotatedElement the {@code AnnotatedElement} from which to get the annotation
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @return the matching annotation, or {@code null} if not found
@@ -144,10 +147,13 @@ public abstract class AnnotationUtils {
}
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method}.
* Get a single {@link Annotation} of {@code annotationType} from the
* supplied {@link Method}, where the method is either directly annotated
* or meta-annotated with the {@code annotationType}.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* <p>Meta-annotations will be searched if the annotation is not
* <em>directly present</em> on the supplied method.
* <p>Note that this method supports only a single level of meta-annotations.
* For support for arbitrary levels of meta-annotations, use
* {@link #findAnnotation(Method, Class)} instead.
* @param method the method to look for annotations on
* @param annotationType the annotation type to look for
* @return the matching annotation, or {@code null} if not found
@@ -256,26 +262,91 @@ public abstract class AnnotationUtils {
}
/**
* Find a single {@link Annotation} of {@code annotationType} from the supplied
* Find a single {@link Annotation} of {@code annotationType} on the
* supplied {@link AnnotatedElement}.
* <p>Meta-annotations will be searched if the annotation is not
* <em>directly present</em> on the supplied element.
* <p><strong>Warning</strong>: this method operates generically on
* annotated elements. In other words, this method does not execute
* specialized search algorithms for classes or methods. If you require
* the more specific semantics of {@link #findAnnotation(Class, Class)}
* or {@link #findAnnotation(Method, Class)}, invoke one of those methods
* instead.
* @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @return the matching annotation, or {@code null} if not found
* @since 4.2
*/
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
// Do NOT store result in the findAnnotationCache since doing so could break
// findAnnotation(Class, Class) and findAnnotation(Method, Class).
return findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>());
}
/**
* Perform the search algorithm for {@link #findAnnotation(AnnotatedElement, Class)}
* avoiding endless recursion by tracking which annotations have already
* been <em>visited</em>.
* @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
* @param annotationType the annotation type to look for, both locally and as a meta-annotation
* @param visited the set of annotations that have already been visited
* @return the matching annotation, or {@code null} if not found
* @since 4.2
*/
@SuppressWarnings("unchecked")
private static <T extends Annotation> T findAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType, Set<Annotation> visited) {
Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
try {
Annotation[] anns = annotatedElement.getDeclaredAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().equals(annotationType)) {
return (T) ann;
}
}
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
T annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
catch (Exception ex) {
// Assuming nested Class values not resolvable within annotation attributes...
logIntrospectionFailure(annotatedElement, ex);
}
return null;
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied
* {@link Method}, traversing its super methods (i.e., from superclasses and
* interfaces) if no annotation can be found on the given method itself.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* <p>Meta-annotations will be searched if the annotation is not
* <em>directly present</em> on the method.
* <p>Annotations on methods are not inherited by default, so we need to handle
* this explicitly.
* <p>Meta-annotations will <em>not</em> be searched.
* @param method the method to look for annotations on
* @param annotationType the annotation type to look for
* @return the matching annotation, or {@code null} if not found
* @see #getAnnotation(Method, Class)
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType);
A result = (A) findAnnotationCache.get(cacheKey);
if (result == null) {
result = getAnnotation(method, annotationType);
Class<?> clazz = method.getDeclaringClass();
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);
if (result == null) {
result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
}
Class<?> clazz = method.getDeclaringClass();
while (result == null) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class)) {
@@ -283,7 +354,8 @@ public abstract class AnnotationUtils {
}
try {
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
result = getAnnotation(equivalentMethod, annotationType);
Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
result = findAnnotation((AnnotatedElement) resolvedEquivalentMethod, annotationType);
}
catch (NoSuchMethodException ex) {
// No equivalent method found
@@ -292,9 +364,10 @@ public abstract class AnnotationUtils {
result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
}
}
if (result != null) {
findAnnotationCache.put(cacheKey, result);
}
}
if (result != null) {
findAnnotationCache.put(cacheKey, result);
}
return result;
}
@@ -680,7 +753,7 @@ public abstract class AnnotationUtils {
}
/**
* Retrieve the <em>value</em> of the {@code &quot;value&quot;} attribute of a
* Retrieve the <em>value</em> of the {@code value} attribute of a
* single-element Annotation, given an annotation instance.
* @param annotation the annotation instance from which to retrieve the value
* @return the attribute value, or {@code null} if not found
@@ -712,7 +785,7 @@ public abstract class AnnotationUtils {
}
/**
* Retrieve the <em>default value</em> of the {@code &quot;value&quot;} attribute
* Retrieve the <em>default value</em> of the {@code value} attribute
* of a single-element Annotation, given an annotation instance.
* @param annotation the annotation instance from which to retrieve the default value
* @return the default value, or {@code null} if not found
@@ -737,7 +810,7 @@ public abstract class AnnotationUtils {
}
/**
* Retrieve the <em>default value</em> of the {@code &quot;value&quot;} attribute
* Retrieve the <em>default value</em> of the {@code value} attribute
* of a single-element Annotation, given the {@link Class annotation type}.
* @param annotationType the <em>annotation type</em> for which the default value should be retrieved
* @return the default value, or {@code null} if not found