From 270308dfd9f2a1f4889c5b8ec3f759a2aa632d45 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 6 May 2015 18:29:26 +0200 Subject: [PATCH] Document "get vs. find" semantics in AnnotatedElementUtils Issue: SPR-11514 --- .../annotation/AnnotatedElementUtils.java | 202 ++++++++++++------ 1 file changed, 135 insertions(+), 67 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 382e3076274..d5568b2663c 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -30,13 +30,59 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** - * Utility class used to collect and merge all annotation attributes on a - * given {@link AnnotatedElement}, including those declared via meta-annotations. + * General utility methods for working with annotations and meta-annotations + * present on {@link AnnotatedElement AnnotatedElements}. + * + *

{@code AnnotatedElementUtils} defines the public API for Spring's + * meta-annotation programming model with support for annotation attribute + * overrides. If you do not need support for annotation attribute overrides, + * consider using {@link AnnotationUtils} instead. + * + *

Note that the features of this class are not provided by the JDK's + * introspection facilities themselves. + * + *

Annotation Attribute Override Support

+ *

Support for meta-annotations with attribute overrides in + * composed annotations is provided by all + * {@code getAnnotationAttributes()} and {@code findAnnotationAttributes()} + * methods. + * + *

Find vs. Get Semantics

+ *

The search algorithms used by methods in this class follow either + * find or get semantics. Consult the Javadoc for each + * individual method for details on which search algorithm is used. + * + *

Get semantics are limited to searching for annotations + * that are either present on an {@code AnnotatedElement} (i.e., + * declared locally or {@linkplain java.lang.annotation.Inherited inherited}) + * or declared within the annotation hierarchy above an {@code AnnotatedElement}. + * + *

Find semantics are much more exhaustive, providing + * get semantics plus support for the following: + * + *

+ * + *

Support for {@code @Inherited}

+ *

Methods following get semantics will honor the contract of + * Java's {@link java.lang.annotation.Inherited @Inherited} annotation. + * However, methods following find semantics will ignore the + * presence of {@code @Inherited} since the find search algorithm + * manually traverses type and method hierarchies and thereby implicitly + * supports annotation inheritance without the need for {@code @Inherited}. * * @author Phillip Webb * @author Juergen Hoeller * @author Sam Brannen * @since 4.0 + * @see AnnotationUtils + * @see AnnotationAttributes + * @see BridgeMethodResolver */ public class AnnotatedElementUtils { @@ -44,12 +90,12 @@ public class AnnotatedElementUtils { /** - * Get the fully qualified class names of all meta-annotation + * Get the fully qualified class names of all meta-annotation * types present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * - *

This method finds all meta-annotations in the annotation hierarchy - * above the specified annotation. + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the annotation type on which to find @@ -66,12 +112,12 @@ public class AnnotatedElementUtils { } /** - * Get the fully qualified class names of all meta-annotation + * Get the fully qualified class names of all meta-annotation * types present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * - *

This method finds all meta-annotations in the annotation hierarchy - * above the specified annotation. + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -90,7 +136,7 @@ public class AnnotatedElementUtils { try { Annotation annotation = getAnnotation(element, annotationType); if (annotation != null) { - processWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor() { + searchWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor() { @Override public Object process(Annotation annotation, int metaDepth) { @@ -112,8 +158,8 @@ public class AnnotatedElementUtils { * a composed annotation that is meta-annotated with an * annotation of the specified {@code annotationType}. * - *

This method finds all meta-annotations in the annotation hierarchy - * above the specified element. + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the meta-annotation @@ -125,7 +171,7 @@ public class AnnotatedElementUtils { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); - return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { + return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { boolean found = annotation.annotationType().getName().equals(annotationType); @@ -142,6 +188,9 @@ public class AnnotatedElementUtils { *

If this method returns {@code true}, then {@link #getAnnotationAttributes} * will return a non-null value. * + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty @@ -151,7 +200,7 @@ public class AnnotatedElementUtils { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); - return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { + return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { boolean found = annotation.annotationType().getName().equals(annotationType); @@ -161,13 +210,16 @@ public class AnnotatedElementUtils { } /** - * Get annotation attributes of the specified {@code annotationType} - * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * Get 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. * *

Delegates to {@link #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. * + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty @@ -180,10 +232,13 @@ public class AnnotatedElementUtils { } /** - * Get annotation attributes of the specified {@code annotationType} - * in the annotation hierarchy of the supplied {@link AnnotatedElement}, + * Get 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. * + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty @@ -197,25 +252,26 @@ public class AnnotatedElementUtils { */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return processWithGetSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType, + return searchWithGetSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap)); } /** - * 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. + * Find annotation attributes of the specified {@code annotationType} + * within annotation hierarchies above the supplied + * {@link AnnotatedElement} and merge the results into an + * {@link AnnotationAttributes} map. * - *

If the annotated element is a class, this algorithm will additionally - * search on interfaces and superclasses. - *

If the annotated element is a method, this algorithm will additionally - * search on methods in interfaces and superclasses. + *

This method follows find semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the annotation type to find; never {@code null} * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found * @since 4.2 + * @see #findAnnotationAttributes(AnnotatedElement, String) + * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, Class annotationType) { @@ -224,14 +280,13 @@ public class AnnotatedElementUtils { } /** - * 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. + * Find annotation attributes of the specified {@code annotationType} + * within annotation hierarchies above the supplied + * {@link AnnotatedElement} and merge the results into an + * {@link AnnotationAttributes} map. * - *

If the annotated element is a class, this algorithm will additionally - * search on interfaces and superclasses. - *

If the annotated element is a method, this algorithm will additionally - * search on methods in interfaces and superclasses. + *

This method follows find semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -239,20 +294,21 @@ public class AnnotatedElementUtils { * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found * @since 4.2 + * @see #findAnnotationAttributes(AnnotatedElement, Class) + * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { return findAnnotationAttributes(element, annotationType, false, false); } /** - * 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. + * Find annotation attributes of the specified {@code annotationType} + * within annotation hierarchies above the supplied + * {@link AnnotatedElement} and merge the results into an + * {@link AnnotationAttributes} map. * - *

If the annotated element is a class, this algorithm will additionally - * search on interfaces and superclasses. - *

If the annotated element is a method, this algorithm will additionally - * search on methods in interfaces and superclasses. + *

This method follows find semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -265,6 +321,9 @@ public class AnnotatedElementUtils { * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found * @since 4.2 + * @see #findAnnotationAttributes(AnnotatedElement, Class) + * @see #findAnnotationAttributes(AnnotatedElement, String) + * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { @@ -273,9 +332,10 @@ public class AnnotatedElementUtils { } /** - * 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. + * Find annotation attributes of the specified {@code annotationType} + * within annotation hierarchies above the supplied + * {@link AnnotatedElement} and merge the results into an + * {@link AnnotationAttributes} map. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -296,24 +356,29 @@ public class AnnotatedElementUtils { * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found * @since 4.2 + * @see #searchWithFindSemantics + * @see MergedAnnotationAttributesProcessor */ private static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, + return searchWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergedAnnotationAttributesProcessor( annotationType, classValuesAsString, nestedAnnotationsAsMap)); } /** - * Get the annotation attributes of all annotations + * Get the annotation attributes of all annotations * of the specified {@code annotationType} in the annotation hierarchy above * the supplied {@link AnnotatedElement} and store the results in a * {@link MultiValueMap}. * *

Note: in contrast to {@link #getAnnotationAttributes(AnnotatedElement, String)}, - * this method does not take attribute overrides into account. + * this method does not support attribute overrides. + * + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -328,13 +393,16 @@ public class AnnotatedElementUtils { } /** - * Get the annotation attributes of all annotations + * Get the annotation attributes of all annotations * of the specified {@code annotationType} in the annotation hierarchy above * the supplied {@link AnnotatedElement} and store the results in a * {@link MultiValueMap}. * *

Note: in contrast to {@link #getAnnotationAttributes(AnnotatedElement, String)}, - * this method does not take attribute overrides into account. + * this method does not support attribute overrides. + * + *

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level Javadoc}. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -353,7 +421,7 @@ public class AnnotatedElementUtils { final MultiValueMap attributesMap = new LinkedMultiValueMap(); - processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { + searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Void process(Annotation annotation, int metaDepth) { @@ -375,8 +443,8 @@ public class AnnotatedElementUtils { } /** - * Process all annotations of the specified {@code annotationType} and - * recursively all meta-annotations on the specified {@code element}. + * Search for annotations of the specified {@code annotationType} on + * the specified {@code element}, following get semantics. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -384,9 +452,9 @@ public class AnnotatedElementUtils { * @param processor the processor to delegate to * @return the result of the processor, potentially {@code null} */ - private static T processWithGetSemantics(AnnotatedElement element, String annotationType, Processor processor) { + private static T searchWithGetSemantics(AnnotatedElement element, String annotationType, Processor processor) { try { - return processWithGetSemantics(element, annotationType, processor, new HashSet(), 0); + return searchWithGetSemantics(element, annotationType, processor, new HashSet(), 0); } catch (Throwable ex) { throw new IllegalStateException("Failed to introspect annotations on " + element, ex); @@ -394,7 +462,7 @@ public class AnnotatedElementUtils { } /** - * Perform the search algorithm for the {@link #processWithGetSemantics} + * Perform the search algorithm for the {@link #searchWithGetSemantics} * method, avoiding endless recursion by tracking which annotated elements * have already been visited. * @@ -410,7 +478,7 @@ public class AnnotatedElementUtils { * @param metaDepth the meta-depth of the annotation * @return the result of the processor, potentially {@code null} */ - private static T processWithGetSemantics(AnnotatedElement element, String annotationType, + private static T searchWithGetSemantics(AnnotatedElement element, String annotationType, Processor processor, Set visited, int metaDepth) { Assert.notNull(element, "AnnotatedElement must not be null"); @@ -435,7 +503,7 @@ public class AnnotatedElementUtils { // Search in meta annotations on local annotations for (Annotation annotation : annotations) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { - T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor, + T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited, metaDepth + 1); if (result != null) { processor.postProcess(annotation, result); @@ -453,8 +521,8 @@ public class AnnotatedElementUtils { } /** - * Process all annotations of the specified {@code annotationType} and - * recursively all meta-annotations on the specified {@code element}. + * Search for annotations of the specified {@code annotationType} on + * the specified {@code element}, following find semantics. * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation @@ -470,12 +538,12 @@ public class AnnotatedElementUtils { * @param processor the processor to delegate to * @return the result of the processor, potentially {@code null} */ - private static T processWithFindSemantics(AnnotatedElement element, String annotationType, + private static T searchWithFindSemantics(AnnotatedElement element, String annotationType, boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses, Processor processor) { try { - return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, + return searchWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet(), 0); } catch (Throwable ex) { @@ -484,7 +552,7 @@ public class AnnotatedElementUtils { } /** - * Perform the search algorithm for the {@link #processWithFindSemantics} + * Perform the search algorithm for the {@link #searchWithFindSemantics} * method, avoiding endless recursion by tracking which annotated elements * have already been visited. * @@ -508,7 +576,7 @@ public class AnnotatedElementUtils { * @param metaDepth the meta-depth of the annotation * @return the result of the processor, potentially {@code null} */ - private static T processWithFindSemantics(AnnotatedElement element, String annotationType, + private static T searchWithFindSemantics(AnnotatedElement element, String annotationType, boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses, Processor processor, Set visited, int metaDepth) { @@ -536,7 +604,7 @@ public class AnnotatedElementUtils { // Search in meta annotations on local annotations for (Annotation annotation : annotations) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { - T result = processWithFindSemantics(annotation.annotationType(), annotationType, + T result = searchWithFindSemantics(annotation.annotationType(), annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1); if (result != null) { @@ -551,7 +619,7 @@ public class AnnotatedElementUtils { // Search on possibly bridged method Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - T result = processWithFindSemantics(resolvedMethod, annotationType, searchOnInterfaces, + T result = searchWithFindSemantics(resolvedMethod, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth); if (result != null) { @@ -582,7 +650,7 @@ public class AnnotatedElementUtils { Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod); - result = processWithFindSemantics(resolvedEquivalentMethod, annotationType, + result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth); if (result != null) { @@ -612,7 +680,7 @@ public class AnnotatedElementUtils { // Search on interfaces if (searchOnInterfaces) { for (Class ifc : clazz.getInterfaces()) { - T result = processWithFindSemantics(ifc, annotationType, searchOnInterfaces, + T result = searchWithFindSemantics(ifc, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth); if (result != null) { @@ -625,7 +693,7 @@ public class AnnotatedElementUtils { if (searchOnSuperclasses) { Class superclass = clazz.getSuperclass(); if (superclass != null && !superclass.equals(Object.class)) { - T result = processWithFindSemantics(superclass, annotationType, searchOnInterfaces, + T result = searchWithFindSemantics(superclass, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth); if (result != null) { @@ -650,7 +718,7 @@ public class AnnotatedElementUtils { if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) { try { Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes()); - T result = processWithFindSemantics(equivalentMethod, annotationType, searchOnInterfaces, + T result = searchWithFindSemantics(equivalentMethod, annotationType, searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, visited, metaDepth);