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:
parent
666d1cecc8
commit
b9b0b78fa1
|
|
@ -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 "value"} 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 "value"} 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 "value"} 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
|
||||
|
|
|
|||
|
|
@ -50,23 +50,47 @@ public class AnnotationUtilsTests {
|
|||
|
||||
@Test
|
||||
public void findMethodAnnotationOnLeaf() throws Exception {
|
||||
Method m = Leaf.class.getMethod("annotatedOnLeaf", (Class[]) null);
|
||||
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
||||
assertNotNull(m.getAnnotation(Order.class));
|
||||
assertNotNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationWithMetaAnnotationOnLeaf() throws Exception {
|
||||
Method m = Leaf.class.getMethod("metaAnnotatedOnLeaf");
|
||||
assertNull(m.getAnnotation(Order.class));
|
||||
assertNotNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationWithMetaMetaAnnotationOnLeaf() throws Exception {
|
||||
Method m = Leaf.class.getMethod("metaMetaAnnotatedOnLeaf");
|
||||
assertNull(m.getAnnotation(Component.class));
|
||||
assertNull(getAnnotation(m, Component.class));
|
||||
assertNotNull(findAnnotation(m, Component.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationOnRoot() throws Exception {
|
||||
Method m = Leaf.class.getMethod("annotatedOnRoot", (Class[]) null);
|
||||
Method m = Leaf.class.getMethod("annotatedOnRoot");
|
||||
assertNotNull(m.getAnnotation(Order.class));
|
||||
assertNotNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationWithMetaAnnotationOnRoot() throws Exception {
|
||||
Method m = Leaf.class.getMethod("metaAnnotatedOnRoot");
|
||||
assertNull(m.getAnnotation(Order.class));
|
||||
assertNotNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationOnRootButOverridden() throws Exception {
|
||||
Method m = Leaf.class.getMethod("overrideWithoutNewAnnotation", (Class[]) null);
|
||||
Method m = Leaf.class.getMethod("overrideWithoutNewAnnotation");
|
||||
assertNull(m.getAnnotation(Order.class));
|
||||
assertNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
|
|
@ -74,7 +98,7 @@ public class AnnotationUtilsTests {
|
|||
|
||||
@Test
|
||||
public void findMethodAnnotationNotAnnotated() throws Exception {
|
||||
Method m = Leaf.class.getMethod("notAnnotated", (Class[]) null);
|
||||
Method m = Leaf.class.getMethod("notAnnotated");
|
||||
assertNull(findAnnotation(m, Order.class));
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +109,7 @@ public class AnnotationUtilsTests {
|
|||
assertNull(m.getAnnotation(Order.class));
|
||||
assertNull(getAnnotation(m, Order.class));
|
||||
assertNotNull(findAnnotation(m, Order.class));
|
||||
// TODO: actually found on OpenJDK 8 b99 and higher!
|
||||
// TODO: getAnnotation() on bridge method actually found on OpenJDK 8 b99 and higher!
|
||||
// assertNull(m.getAnnotation(Transactional.class));
|
||||
assertNotNull(getAnnotation(m, Transactional.class));
|
||||
assertNotNull(findAnnotation(m, Transactional.class));
|
||||
|
|
@ -462,6 +486,10 @@ public class AnnotationUtilsTests {
|
|||
public void annotatedOnRoot() {
|
||||
}
|
||||
|
||||
@Meta1
|
||||
public void metaAnnotatedOnRoot() {
|
||||
}
|
||||
|
||||
public void overrideToAnnotate() {
|
||||
}
|
||||
|
||||
|
|
@ -483,6 +511,14 @@ public class AnnotationUtilsTests {
|
|||
public void annotatedOnLeaf() {
|
||||
}
|
||||
|
||||
@Meta1
|
||||
public void metaAnnotatedOnLeaf() {
|
||||
}
|
||||
|
||||
@MetaMeta
|
||||
public void metaMetaAnnotatedOnLeaf() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Order(1)
|
||||
public void overrideToAnnotate() {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
|
@ -51,7 +50,12 @@ import static org.junit.Assert.*;
|
|||
import static org.springframework.transaction.event.TransactionPhase.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link TransactionalEventListener @TransactionalEventListener}
|
||||
* support
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
*/
|
||||
public class TransactionalEventListenerTests {
|
||||
|
||||
|
|
@ -265,8 +269,7 @@ public class TransactionalEventListenerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("not an event listener if not tagged")
|
||||
public void afterCommitMetaAnnotation() {
|
||||
public void afterCommitMetaAnnotation() throws Exception {
|
||||
load(AfterCommitMetaAnnotationTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
|
|
@ -279,7 +282,6 @@ public class TransactionalEventListenerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("not an event listener if not tagged + condition found on wrong annotation")
|
||||
public void conditionFoundOnMetaAnnotation() {
|
||||
load(AfterCommitMetaAnnotationTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue