Support annotations on interfaces in AnnotatedElementUtils

This commit introduces support in AnnotatedElementUtils for finding
annotations declared on interfaces at the type level.

NB: this commit does not include support for finding annotations
declared on interface methods.

In order to maintain backward compatibility with @Transactional
annotation attribute processing, a new getAnnotationAttributes() method
has been added to AnnotatedElementUtils that provides a flag to control
whether interfaces should be searched.
SpringTransactionAnnotationParser and JtaTransactionAnnotationParser
have been updated accordingly to ensure that interfaces are not
unintentionally searched in the @Transactional resolution process.

This commit also introduces additional tests and updates TODOs for
SPR-12738.

Issue: SPR-12944, SPR-12738
This commit is contained in:
Sam Brannen
2015-04-22 21:32:06 +02:00
parent 9b7fd8be4d
commit 7f0f04dfe3
5 changed files with 210 additions and 47 deletions

View File

@@ -27,8 +27,8 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Utility class used to collect all annotation values including those declared on
* meta-annotations.
* Utility class used to collect all annotation attributes, including those
* declared on meta-annotations.
*
* @author Phillip Webb
* @author Juergen Hoeller
@@ -39,7 +39,7 @@ public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
final Set<String> types = new LinkedHashSet<String>();
process(element, annotationType, false, new Processor<Object>() {
process(element, annotationType, true, false, new Processor<Object>() {
@Override
public Object process(Annotation annotation, int metaDepth) {
if (metaDepth > 0) {
@@ -55,7 +55,7 @@ public class AnnotatedElementUtils {
}
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int metaDepth) {
if (metaDepth > 0) {
@@ -70,7 +70,7 @@ public class AnnotatedElementUtils {
}
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
return Boolean.TRUE.equals(process(element, annotationType, true, false, new Processor<Boolean>() {
@Override
public Boolean process(Annotation annotation, int metaDepth) {
return Boolean.TRUE;
@@ -81,14 +81,59 @@ public class AnnotatedElementUtils {
}));
}
/**
* Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)},
* supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}.
*
* @param element the annotated element
* @param annotationType the annotation type to find
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
return getAnnotationAttributes(element, annotationType, false, false);
}
/**
* Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean)},
* supplying {@code true} for {@code searchInterfaces} and {@code false} for {@code searchClassHierarchy}.
*
* @param element the annotated element
* @param annotationType the annotation type to find
* @param classValuesAsString whether to convert Class references into
* Strings or to preserve them as Class references
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances
* into {@link AnnotationAttributes} maps or to preserve them as Annotation
* instances
* @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean, boolean, boolean)
*/
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
return getAnnotationAttributes(element, annotationType, true, false, classValuesAsString,
nestedAnnotationsAsMap);
}
return process(element, annotationType, false, new Processor<AnnotationAttributes>() {
/**
* Find annotation attributes of the specified {@code annotationType} in
* the annotation hierarchy of the supplied {@link AnnotatedElement},
* and merge the results into an {@link AnnotationAttributes} map.
*
* @param element the annotated element
* @param annotationType the annotation type to find
* @param searchInterfaces whether or not to search on interfaces, if the
* annotated element is a class
* @param searchClassHierarchy whether or not to search the class hierarchy
* recursively, if the annotated element is a class
* @param classValuesAsString whether to convert Class references into
* Strings or to preserve them as Class references
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances
* into {@link AnnotationAttributes} maps or to preserve them as Annotation
* instances
*/
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
boolean searchInterfaces, boolean searchClassHierarchy, final boolean classValuesAsString,
final boolean nestedAnnotationsAsMap) {
return process(element, annotationType, searchInterfaces, searchClassHierarchy, new Processor<AnnotationAttributes>() {
@Override
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
@@ -115,7 +160,7 @@ public class AnnotatedElementUtils {
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
process(element, annotationType, false, new Processor<Void>() {
process(element, annotationType, true, false, new Processor<Void>() {
@Override
public Void process(Annotation annotation, int metaDepth) {
if (annotation.annotationType().getName().equals(annotationType)) {
@@ -144,22 +189,26 @@ public class AnnotatedElementUtils {
/**
* Process all annotations of the specified {@code annotationType} and
* recursively all meta-annotations on the specified {@code element}.
* <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought
*
* <p>If the {@code searchClassHierarchy} flag is {@code true} and the sought
* annotation is neither <em>directly present</em> on the given element nor
* present on the given element as a meta-annotation, then the algorithm will
* recursively search through the class hierarchy of the given element.
*
* @param element the annotated element
* @param annotationType the annotation type to find
* @param traverseClassHierarchy whether or not to traverse up the class
* hierarchy recursively
* @param searchInterfaces whether or not to search on interfaces, if the
* annotated element is a class
* @param searchClassHierarchy whether or not to search the class hierarchy
* recursively, if the annotated element is a class
* @param processor the processor to delegate to
* @return the result of the processor
*/
private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
Processor<T> processor) {
private static <T> T process(AnnotatedElement element, String annotationType, boolean searchInterfaces,
boolean searchClassHierarchy, Processor<T> processor) {
try {
return doProcess(element, annotationType, traverseClassHierarchy, processor,
return doProcess(element, annotationType, searchInterfaces, searchClassHierarchy, processor,
new HashSet<AnnotatedElement>(), 0);
}
catch (Throwable ex) {
@@ -171,54 +220,79 @@ public class AnnotatedElementUtils {
* Perform the search algorithm for the {@link #process} method, avoiding
* endless recursion by tracking which annotated elements have already been
* <em>visited</em>.
*
* <p>The {@code metaDepth} parameter represents the depth of the annotation
* relative to the initial element. For example, an annotation that is
* <em>present</em> on the element will have a depth of 0; a meta-annotation
* will have a depth of 1; and a meta-meta-annotation will have a depth of 2.
*
* @param element the annotated element
* @param annotationType the annotation type to find
* @param traverseClassHierarchy whether or not to traverse up the class
* hierarchy recursively
* @param searchInterfaces whether or not to search on interfaces, if the
* annotated element is a class
* @param searchClassHierarchy whether or not to search the class hierarchy
* recursively, if the annotated element is a class
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the depth of the annotation relative to the initial element
* @return the result of the processor
*/
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean searchInterfaces,
boolean searchClassHierarchy, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
if (visited.add(element)) {
try {
// Local annotations: either directly declared or inherited.
Annotation[] annotations =
(traverseClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
(searchClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
// Search in local annotations
for (Annotation annotation : annotations) {
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
T result = processor.process(annotation, metaDepth);
if (result != null) {
return result;
}
result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
processor, visited, metaDepth + 1);
result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
searchClassHierarchy, processor, visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
// Search in meta annotations on location annotations
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
processor, visited, metaDepth);
T result = doProcess(annotation.annotationType(), annotationType, searchInterfaces,
searchClassHierarchy, processor, visited, metaDepth);
if (result != null) {
processor.postProcess(annotation, result);
return result;
}
}
}
if (traverseClassHierarchy && element instanceof Class) {
// Search on interfaces
if (searchInterfaces && element instanceof Class) {
Class<?> clazz = (Class<?>) element;
for (Class<?> ifc : clazz.getInterfaces()) {
T result = doProcess(ifc, annotationType, searchInterfaces, searchClassHierarchy, processor,
visited, metaDepth);
if (result != null) {
return result;
}
}
}
// Search on superclass
if (searchClassHierarchy && element instanceof Class) {
Class<?> superclass = ((Class<?>) element).getSuperclass();
if (superclass != null && !superclass.equals(Object.class)) {
T result = doProcess(superclass, annotationType, true, processor, visited, metaDepth);
T result = doProcess(superclass, annotationType, searchInterfaces, searchClassHierarchy,
processor, visited, metaDepth);
if (result != null) {
return result;
}