Favor 'local' annotations over inherited ones
Prior to this commit, the implementations of findAnnotation() in AnnotationUtils and getAnnotationAttributes() in AnnotatedElementUtils favored inherited annotations and inherited composed annotations over composed annotations that are declared closer to the starting class passed to these methods. This commit addresses this issue as follows: - Refactored AnnotationUtils to use getDeclaredAnnotation() and getDeclaredAnnotations() instead of getAnnotation() and getAnnotations() where appropriate. - AnnotatedElementUtils.doProcess() supports a traverseClassHierarchy flag to control whether the class hierarchy should be traversed, using getDeclaredAnnotations() instead of getAnnotations() if the flag is true. - Overhauled Javadoc in AnnotatedElementUtils. Issue: SPR-11475
This commit is contained in:
@@ -26,6 +26,8 @@ import java.util.Set;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
/**
|
||||
* Utility class used to collect all annotation values including those declared on
|
||||
* meta-annotations.
|
||||
@@ -39,14 +41,16 @@ public class AnnotatedElementUtils {
|
||||
|
||||
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||
final Set<String> types = new LinkedHashSet<String>();
|
||||
process(element, annotationType, new Processor<Object>() {
|
||||
process(element, annotationType, true, new Processor<Object>() {
|
||||
|
||||
@Override
|
||||
public Object process(Annotation annotation, int depth) {
|
||||
if (depth > 0) {
|
||||
public Object process(Annotation annotation, int metaDepth) {
|
||||
if (metaDepth > 0) {
|
||||
types.add(annotation.annotationType().getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, Object result) {
|
||||
}
|
||||
@@ -55,14 +59,16 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
|
||||
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
|
||||
return Boolean.TRUE.equals(process(element, annotationType, true, new Processor<Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int depth) {
|
||||
if (depth > 0) {
|
||||
return true;
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
if (metaDepth > 0) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, Boolean result) {
|
||||
}
|
||||
@@ -70,11 +76,13 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
|
||||
return Boolean.TRUE.equals(process(element, annotationType, new Processor<Boolean>() {
|
||||
return Boolean.TRUE.equals(process(element, annotationType, true, new Processor<Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int depth) {
|
||||
return true;
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, Boolean result) {
|
||||
}
|
||||
@@ -85,20 +93,21 @@ public class AnnotatedElementUtils {
|
||||
return getAnnotationAttributes(element, annotationType, false, false);
|
||||
}
|
||||
|
||||
public static AnnotationAttributes getAnnotationAttributes(
|
||||
AnnotatedElement element, String annotationType,
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
||||
|
||||
return process(element, annotationType, new Processor<AnnotationAttributes>() {
|
||||
return process(element, annotationType, true, new Processor<AnnotationAttributes>() {
|
||||
|
||||
@Override
|
||||
public AnnotationAttributes process(Annotation annotation, int depth) {
|
||||
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
|
||||
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, AnnotationAttributes result) {
|
||||
for (String key : result.keySet()) {
|
||||
if (!"value".equals(key)) {
|
||||
Object value = AnnotationUtils.getValue(annotation, key);
|
||||
if (!VALUE.equals(key)) {
|
||||
Object value = getValue(annotation, key);
|
||||
if (value != null) {
|
||||
result.put(key, value);
|
||||
}
|
||||
@@ -108,28 +117,33 @@ public class AnnotatedElementUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static MultiValueMap<String, Object> getAllAnnotationAttributes(
|
||||
AnnotatedElement element, final String annotationType,
|
||||
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
||||
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
|
||||
final String annotationType) {
|
||||
return getAllAnnotationAttributes(element, annotationType, false, false);
|
||||
}
|
||||
|
||||
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
|
||||
final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
||||
|
||||
final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
|
||||
process(element, annotationType, new Processor<Void>() {
|
||||
process(element, annotationType, false, new Processor<Void>() {
|
||||
|
||||
@Override
|
||||
public Void process(Annotation annotation, int depth) {
|
||||
public Void process(Annotation annotation, int metaDepth) {
|
||||
if (annotation.annotationType().getName().equals(annotationType)) {
|
||||
for (Map.Entry<String, Object> entry :
|
||||
AnnotationUtils.getAnnotationAttributes(
|
||||
annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
|
||||
for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes(annotation,
|
||||
classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
|
||||
attributes.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, Void result) {
|
||||
for (String key : attributes.keySet()) {
|
||||
if (!"value".equals(key)) {
|
||||
Object value = AnnotationUtils.getValue(annotation, key);
|
||||
if (!VALUE.equals(key)) {
|
||||
Object value = getValue(annotation, key);
|
||||
if (value != null) {
|
||||
attributes.add(key, value);
|
||||
}
|
||||
@@ -141,45 +155,91 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all annotations of the specified annotation type and recursively all
|
||||
* meta-annotations on the specified type.
|
||||
* 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
|
||||
* 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. Only items of the specified
|
||||
* type or meta-annotations of the specified type will be processed.
|
||||
* @param processor the processor
|
||||
* @param annotationType the annotation type to find
|
||||
* @param traverseClassHierarchy whether or not to traverse up the class
|
||||
* hierarchy recursively
|
||||
* @param processor the processor to delegate to
|
||||
* @return the result of the processor
|
||||
*/
|
||||
private static <T> T process(AnnotatedElement element, String annotationType, Processor<T> processor) {
|
||||
return doProcess(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
|
||||
private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
|
||||
Processor<T> processor) {
|
||||
return doProcess(element, annotationType, traverseClassHierarchy, processor, new HashSet<AnnotatedElement>(), 0);
|
||||
}
|
||||
|
||||
private static <T> T doProcess(AnnotatedElement element, String annotationType,
|
||||
Processor<T> processor, Set<AnnotatedElement> visited, int depth) {
|
||||
/**
|
||||
* 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 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) {
|
||||
|
||||
if (visited.add(element)) {
|
||||
for (Annotation annotation : element.getAnnotations()) {
|
||||
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
|
||||
T result = processor.process(annotation, depth);
|
||||
|
||||
Annotation[] annotations = traverseClassHierarchy ? element.getDeclaredAnnotations()
|
||||
: element.getAnnotations();
|
||||
|
||||
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, processor, visited, depth + 1);
|
||||
result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy, processor,
|
||||
visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Annotation annotation : element.getAnnotations()) {
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
|
||||
T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth);
|
||||
|
||||
for (Annotation annotation : annotations) {
|
||||
if (!isInJavaLangAnnotationPackage(annotation)) {
|
||||
T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
|
||||
processor, visited, metaDepth);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (traverseClassHierarchy && element instanceof Class) {
|
||||
Class<?> superclass = ((Class<?>) element).getSuperclass();
|
||||
if (superclass != null && !superclass.equals(Object.class)) {
|
||||
T result = doProcess(superclass, annotationType, traverseClassHierarchy, processor, visited,
|
||||
metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -192,13 +252,18 @@ public class AnnotatedElementUtils {
|
||||
|
||||
/**
|
||||
* Called to process the annotation.
|
||||
*
|
||||
* <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 annotation the annotation to process
|
||||
* @param depth the depth of the annotation relative to the initial match.
|
||||
* For example, a matched annotation will have a depth of 0, a meta-annotation
|
||||
* 1 and a meta-meta-annotation 2
|
||||
* @param metaDepth the depth of the annotation relative to the initial element
|
||||
* @return the result of the processing or {@code null} to continue
|
||||
*/
|
||||
T process(Annotation annotation, int depth);
|
||||
T process(Annotation annotation, int metaDepth);
|
||||
|
||||
void postProcess(Annotation annotation, T result);
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ public abstract class AnnotationUtils {
|
||||
Set<Annotation> visited) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
|
||||
A annotation = clazz.getAnnotation(annotationType);
|
||||
A annotation = clazz.getDeclaredAnnotation(annotationType);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
@@ -288,7 +288,7 @@ public abstract class AnnotationUtils {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
for (Annotation ann : clazz.getAnnotations()) {
|
||||
for (Annotation ann : clazz.getDeclaredAnnotations()) {
|
||||
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
|
||||
annotation = findAnnotation(ann.annotationType(), annotationType, visited);
|
||||
if (annotation != null) {
|
||||
|
||||
Reference in New Issue
Block a user