summaryrefslogtreecommitdiff
path: root/spring-core/src/main/java/org/springframework/core/annotation
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
committerEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
commitda46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch)
tree52b707fbbccd5b6100088913f32c1cbd00568790 /spring-core/src/main/java/org/springframework/core/annotation
parentc03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff)
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-core/src/main/java/org/springframework/core/annotation')
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java253
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java19
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java466
4 files changed, 666 insertions, 139 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
new file mode 100644
index 00000000..af84f7b5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * Utility class used to collect all annotation values including those declared on
+ * meta-annotations.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 4.0
+ */
+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>() {
+ @Override
+ 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) {
+ }
+ });
+ return (types.isEmpty() ? null : types);
+ }
+
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
+ return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
+ @Override
+ public Boolean process(Annotation annotation, int metaDepth) {
+ if (metaDepth > 0) {
+ return Boolean.TRUE;
+ }
+ return null;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Boolean result) {
+ }
+ }));
+ }
+
+ public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
+ return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
+ @Override
+ public Boolean process(Annotation annotation, int metaDepth) {
+ return Boolean.TRUE;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Boolean result) {
+ }
+ }));
+ }
+
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
+ return getAnnotationAttributes(element, annotationType, false, false);
+ }
+
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
+ final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+
+ return process(element, annotationType, false, new Processor<AnnotationAttributes>() {
+ @Override
+ 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 (!AnnotationUtils.VALUE.equals(key)) {
+ Object value = AnnotationUtils.getValue(annotation, key);
+ if (value != null) {
+ result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, 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, false, new Processor<Void>() {
+ @Override
+ 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()) {
+ attributes.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return null;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Void result) {
+ for (String key : attributes.keySet()) {
+ if (!AnnotationUtils.VALUE.equals(key)) {
+ Object value = AnnotationUtils.getValue(annotation, key);
+ if (value != null) {
+ attributes.add(key, value);
+ }
+ }
+ }
+ }
+ });
+ return (attributes.isEmpty() ? null : attributes);
+ }
+
+ /**
+ * 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
+ * @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, boolean traverseClassHierarchy,
+ Processor<T> processor) {
+
+ try {
+ return doProcess(element, annotationType, traverseClassHierarchy, processor,
+ new HashSet<AnnotatedElement>(), 0);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Failed to introspect annotations: " + element, ex);
+ }
+ }
+
+ /**
+ * 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)) {
+ 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, traverseClassHierarchy,
+ processor, visited, metaDepth + 1);
+ if (result != null) {
+ processor.postProcess(annotation, result);
+ return result;
+ }
+ }
+ }
+ for (Annotation annotation : annotations) {
+ if (!AnnotationUtils.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, true, processor, visited, metaDepth);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Callback interface used to process an annotation.
+ * @param <T> the result type
+ */
+ private static interface Processor<T> {
+
+ /**
+ * 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 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 metaDepth);
+
+ void postProcess(Annotation annotation, T result);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
index 40041e8d..090621d8 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
@@ -16,8 +16,7 @@
package org.springframework.core.annotation;
-import static java.lang.String.format;
-
+import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -27,10 +26,9 @@ import org.springframework.util.StringUtils;
/**
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
- * as read by Spring's reflection- or ASM-based {@link
- * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
- * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
- * as convenience methods for looking up annotation attributes in a type-safe fashion.
+ * as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata}
+ * implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code
+ * as well as convenience methods for looking up annotation attributes in a type-safe fashion.
*
* @author Chris Beams
* @since 3.1.1
@@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
super(map);
}
- /**
- * Return an {@link AnnotationAttributes} instance based on the given map; if the map
- * is already an {@code AnnotationAttributes} instance, it is casted and returned
- * immediately without creating any new instance; otherwise create a new instance by
- * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
- * @param map original source of annotation attribute key/value pairs
- */
- public static AnnotationAttributes fromMap(Map<String, Object> map) {
- if (map == null) {
- return null;
- }
-
- if (map instanceof AnnotationAttributes) {
- return (AnnotationAttributes) map;
- }
-
- return new AnnotationAttributes(map);
- }
public String getString(String attributeName) {
return doGet(attributeName, String.class);
@@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@SuppressWarnings("unchecked")
public <N extends Number> N getNumber(String attributeName) {
- return (N) doGet(attributeName, Integer.class);
+ return (N) doGet(attributeName, Number.class);
}
@SuppressWarnings("unchecked")
@@ -124,14 +104,24 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@SuppressWarnings("unchecked")
private <T> T doGet(String attributeName, Class<T> expectedType) {
Assert.hasText(attributeName, "attributeName must not be null or empty");
- Object value = this.get(attributeName);
- Assert.notNull(value, format("Attribute '%s' not found", attributeName));
- Assert.isAssignable(expectedType, value.getClass(),
- format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
+ Object value = get(attributeName);
+ Assert.notNull(value, String.format("Attribute '%s' not found", attributeName));
+ if (!expectedType.isInstance(value)) {
+ if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
+ Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1);
+ Array.set(arrayValue, 0, value);
+ value = arrayValue;
+ }
+ else {
+ throw new IllegalArgumentException(
+ String.format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
+ }
+ }
return (T) value;
}
+ @Override
public String toString() {
Iterator<Map.Entry<String, Object>> entries = entrySet().iterator();
StringBuilder sb = new StringBuilder("{");
@@ -155,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
}
return String.valueOf(value);
}
+
+
+ /**
+ * Return an {@link AnnotationAttributes} instance based on the given map; if the map
+ * is already an {@code AnnotationAttributes} instance, it is casted and returned
+ * immediately without creating any new instance; otherwise create a new instance by
+ * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
+ * @param map original source of annotation attribute key/value pairs
+ */
+ public static AnnotationAttributes fromMap(Map<String, Object> map) {
+ if (map == null) {
+ return null;
+ }
+ if (map instanceof AnnotationAttributes) {
+ return (AnnotationAttributes) map;
+ }
+ return new AnnotationAttributes(map);
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
index 19ba5fec..f4a83b8f 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
@@ -50,7 +50,7 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
return ((Ordered) obj).getOrder();
}
if (obj != null) {
- Class<?> clazz = (obj instanceof Class ? (Class) obj : obj.getClass());
+ Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
if (order != null) {
return order.value();
@@ -86,4 +86,21 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
}
}
+ /**
+ * Sort the given array or List with a default AnnotationAwareOrderComparator,
+ * if necessary. Simply skips sorting when given any other value.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param value the array or List to sort
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+ public static void sortIfNecessary(Object value) {
+ if (value instanceof Object[]) {
+ sort((Object[]) value);
+ }
+ else if (value instanceof List) {
+ sort((List<?>) value);
+ }
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index 3b4def63..45d7c48c 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -19,31 +19,44 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.WeakHashMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
- * General utility methods for working with annotations, handling bridge methods (which the compiler
- * generates for generic declarations) as well as super methods (for optional &quot;annotation inheritance&quot;).
- * Note that none of this is provided by the JDK's introspection facilities themselves.
+ * General utility methods for working with annotations, handling bridge methods
+ * (which the compiler generates for generic declarations) as well as super methods
+ * (for optional &quot;annotation inheritance&quot;). Note that none of this is
+ * provided by the JDK's introspection facilities themselves.
*
- * <p>As a general rule for runtime-retained annotations (e.g. for transaction control, authorization or service
- * exposure), always use the lookup methods on this class (e.g., {@link #findAnnotation(Method, Class)}, {@link
- * #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) instead of the plain annotation lookup
- * methods in the JDK. You can still explicitly choose between lookup on the given class level only ({@link
- * #getAnnotation(Method, Class)}) and lookup in the entire inheritance hierarchy of the given method ({@link
- * #findAnnotation(Method, Class)}).
+ * <p>As a general rule for runtime-retained annotations (e.g. for transaction
+ * control, authorization, or service exposure), always use the lookup methods
+ * on this class (e.g., {@link #findAnnotation(Method, Class)},
+ * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)})
+ * instead of the plain annotation lookup methods in the JDK. You can still
+ * explicitly choose between a <em>get</em> lookup on the given class level only
+ * ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire
+ * inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}).
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
* @author Mark Fisher
* @author Chris Beams
+ * @author Phillip Webb
* @since 2.0
* @see java.lang.reflect.Method#getAnnotations()
* @see java.lang.reflect.Method#getAnnotation(Class)
@@ -51,42 +64,100 @@ import org.springframework.util.ReflectionUtils;
public abstract class AnnotationUtils {
/** The attribute name for annotations with a single element */
- static final String VALUE = "value";
+ public static final String VALUE = "value";
+
private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>();
+ private static transient Log logger;
+
+
+ /**
+ * Get a single {@link Annotation} of {@code annotationType} from the supplied
+ * annotation: either the given annotation itself or a meta-annotation thereof.
+ * @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 none found
+ * @since 4.0
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) {
+ if (annotationType.isInstance(ann)) {
+ return (T) ann;
+ }
+ try {
+ return ann.annotationType().getAnnotation(annotationType);
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(ann.annotationType(), ex);
+ return null;
+ }
+ }
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied
* Method, Constructor or Field. Meta-annotations will be searched if the annotation
* is not declared locally on the supplied element.
- * @param ae the Method, Constructor or Field from which to get the annotation
+ * @param annotatedElement the Method, Constructor or Field 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 none found
* @since 3.1
*/
- public static <T extends Annotation> T getAnnotation(AnnotatedElement ae, Class<T> annotationType) {
- T ann = ae.getAnnotation(annotationType);
- if (ann == null) {
- for (Annotation metaAnn : ae.getAnnotations()) {
- ann = metaAnn.annotationType().getAnnotation(annotationType);
- if (ann != null) {
- break;
+ public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) {
+ try {
+ T ann = annotatedElement.getAnnotation(annotationType);
+ if (ann == null) {
+ for (Annotation metaAnn : annotatedElement.getAnnotations()) {
+ ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ break;
+ }
}
}
+ return ann;
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ return null;
+ }
+ }
+
+ /**
+ * Get all {@link Annotation Annotations} from the supplied Method, Constructor or Field.
+ * @param annotatedElement the Method, Constructor or Field to retrieve annotations from
+ * @return the annotations found, or {@code null} if not resolvable (e.g. because nested
+ * Class values in annotation attributes failed to resolve at runtime)
+ * @since 4.0.8
+ */
+ public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
+ try {
+ return annotatedElement.getAnnotations();
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ return null;
}
- return ann;
}
/**
* Get all {@link Annotation Annotations} from the supplied {@link Method}.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
- * @param method the method to look for annotations on
+ * @param method the Method to retrieve annotations from
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
*/
public static Annotation[] getAnnotations(Method method) {
- return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
+ try {
+ return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(method, ex);
+ return null;
+ }
}
/**
@@ -99,22 +170,61 @@ public abstract class AnnotationUtils {
*/
public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
- A ann = resolvedMethod.getAnnotation(annotationType);
- if (ann == null) {
- for (Annotation metaAnn : resolvedMethod.getAnnotations()) {
- ann = metaAnn.annotationType().getAnnotation(annotationType);
- if (ann != null) {
- break;
- }
+ return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
+ }
+
+ /**
+ * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
+ * supplied {@link Method}. Deals with both a single direct annotation and repeating
+ * annotations nested within a containing annotation.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param method the method to look for annotations on
+ * @param containerAnnotationType the class of the container that holds the annotations
+ * @param annotationType the annotation type to look for
+ * @return the annotations found
+ * @since 4.0
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
+ Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+
+ Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
+ return getRepeatableAnnotation((AnnotatedElement) resolvedMethod, containerAnnotationType, annotationType);
+ }
+
+ /**
+ * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
+ * supplied {@link AnnotatedElement}. Deals with both a single direct annotation and
+ * repeating annotations nested within a containing annotation.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param annotatedElement the element to look for annotations on
+ * @param containerAnnotationType the class of the container that holds the annotations
+ * @param annotationType the annotation type to look for
+ * @return the annotations found
+ * @since 4.0
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
+ Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+
+ try {
+ if (annotatedElement.getAnnotations().length > 0) {
+ return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement);
}
}
- return ann;
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ }
+ return Collections.emptySet();
}
/**
- * Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method},
- * traversing its super methods if no annotation can be found on the given method itself.
- * <p>Annotations on methods are not inherited by default, so we need to handle this explicitly.
+ * Find a single {@link Annotation} of {@code annotationType} from 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>Annotations on methods are not inherited by default, so we need to handle
+ * this explicitly.
* @param method the method to look for annotations on
* @param annotationType the annotation type to look for
* @return the annotation found, or {@code null} if none
@@ -171,9 +281,15 @@ public abstract class AnnotationUtils {
}
boolean found = false;
for (Method ifcMethod : iface.getMethods()) {
- if (ifcMethod.getAnnotations().length > 0) {
- found = true;
- break;
+ try {
+ if (ifcMethod.getAnnotations().length > 0) {
+ found = true;
+ break;
+ }
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(ifcMethod, ex);
}
}
annotatedInterfaceCache.put(iface, found);
@@ -182,45 +298,82 @@ public abstract class AnnotationUtils {
}
/**
- * Find a single {@link Annotation} of {@code annotationType} from the supplied {@link Class},
- * traversing its interfaces and superclasses if no annotation can be found on the given class itself.
- * <p>This method explicitly handles class-level annotations which are not declared as
- * {@link java.lang.annotation.Inherited inherited} <i>as well as annotations on interfaces</i>.
- * <p>The algorithm operates as follows: Searches for an annotation on the given class and returns
- * it if found. Else searches all interfaces that the given class declares, returning the annotation
- * from the first matching candidate, if any. Else proceeds with introspection of the superclass
- * of the given class, checking the superclass itself; if no annotation found there, proceeds
- * with the interfaces that the superclass declares. Recursing up through the entire superclass
- * hierarchy if no match is found.
+ * Find a single {@link Annotation} of {@code annotationType} on the
+ * supplied {@link Class}, traversing its interfaces, annotations, and
+ * superclasses if the annotation is not <em>present</em> on the given class
+ * itself.
+ * <p>This method explicitly handles class-level annotations which are not
+ * declared as {@link java.lang.annotation.Inherited inherited} <em>as well
+ * as meta-annotations and annotations on interfaces</em>.
+ * <p>The algorithm operates as follows:
+ * <ol>
+ * <li>Search for the annotation on the given class and return it if found.
+ * <li>Recursively search through all interfaces that the given class declares.
+ * <li>Recursively search through all annotations that the given class declares.
+ * <li>Recursively search through the superclass hierarchy of the given class.
+ * </ol>
+ * <p>Note: in this context, the term <em>recursively</em> means that the search
+ * process continues by returning to step #1 with the current interface,
+ * annotation, or superclass as the class to look for annotations on.
* @param clazz the class to look for annotations on
- * @param annotationType the annotation type to look for
- * @return the annotation found, or {@code null} if none found
+ * @param annotationType the type of annotation to look for
+ * @return the annotation if found, or {@code null} if not found
*/
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
+ return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
+ }
+
+ /**
+ * Perform the search algorithm for {@link #findAnnotation(Class, Class)},
+ * avoiding endless recursion by tracking which annotations have already
+ * been <em>visited</em>.
+ * @param clazz the class to look for annotations on
+ * @param annotationType the type of annotation to look for
+ * @param visited the set of annotations that have already been visited
+ * @return the annotation if found, or {@code null} if not found
+ */
+ private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
Assert.notNull(clazz, "Class must not be null");
- A annotation = clazz.getAnnotation(annotationType);
- if (annotation != null) {
- return annotation;
+
+ if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ try {
+ return clazz.getAnnotation(annotationType);
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ return null;
+ }
}
+
for (Class<?> ifc : clazz.getInterfaces()) {
- annotation = findAnnotation(ifc, annotationType);
+ A annotation = findAnnotation(ifc, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
- if (!Annotation.class.isAssignableFrom(clazz)) {
- for (Annotation ann : clazz.getAnnotations()) {
- annotation = findAnnotation(ann.annotationType(), annotationType);
- if (annotation != null) {
- return annotation;
+
+ try {
+ for (Annotation ann : clazz.getDeclaredAnnotations()) {
+ if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
+ A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
+ if (annotation != null) {
+ return annotation;
+ }
}
}
}
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ return null;
+ }
+
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || superclass.equals(Object.class)) {
return null;
}
- return findAnnotation(superclass, annotationType);
+ return findAnnotation(superclass, annotationType, visited);
}
/**
@@ -312,12 +465,18 @@ public abstract class AnnotationUtils {
Assert.notNull(annotationType, "Annotation type must not be null");
Assert.notNull(clazz, "Class must not be null");
boolean declaredLocally = false;
- for (Annotation annotation : clazz.getDeclaredAnnotations()) {
- if (annotation.annotationType().equals(annotationType)) {
- declaredLocally = true;
- break;
+ try {
+ for (Annotation ann : clazz.getDeclaredAnnotations()) {
+ if (ann.annotationType().equals(annotationType)) {
+ declaredLocally = true;
+ break;
+ }
}
}
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ }
return declaredLocally;
}
@@ -343,11 +502,21 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the given annotation's attributes as a Map, preserving all attribute types
- * as-is.
- * <p>Note: As of Spring 3.1.1, the returned map is actually an
- * {@link AnnotationAttributes} instance, however the Map signature of this method has
- * been preserved for binary compatibility.
+ * Determine if the supplied {@link Annotation} is defined in the core JDK
+ * {@code java.lang.annotation} package.
+ * @param annotation the annotation to check (never {@code null})
+ * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
+ */
+ public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
+ Assert.notNull(annotation, "Annotation must not be null");
+ return annotation.annotationType().getName().startsWith("java.lang.annotation");
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as a {@link Map}, preserving all
+ * attribute types as-is.
+ * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
+ * However, the {@code Map} signature has been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for
* @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values
@@ -357,16 +526,15 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the given annotation's attributes as a Map. Equivalent to calling
- * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
+ * Retrieve the given annotation's attributes as a {@link Map}. Equivalent to
+ * calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
* the {@code nestedAnnotationsAsMap} parameter set to {@code false}.
- * <p>Note: As of Spring 3.1.1, the returned map is actually an
- * {@link AnnotationAttributes} instance, however the Map signature of this method has
- * been preserved for binary compatibility.
+ * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
+ * However, the {@code Map} signature has been preserved for binary compatibility.
* @param annotation the annotation to retrieve the attributes for
* @param classValuesAsString whether to turn Class references into Strings (for
- * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
- * preserve them as Class references
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata}
+ * or to preserve them as Class references
* @return the Map of annotation attributes, with attribute names as keys and
* corresponding attribute values as values
*/
@@ -376,13 +544,13 @@ public abstract class AnnotationUtils {
/**
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
- * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation
- * reading capabilities on par with that of the reflection-based
- * {@link org.springframework.core.type.StandardAnnotationMetadata}.
+ * map structure.
+ * <p>This method provides fully recursive annotation reading capabilities on par with
+ * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
* @param annotation the annotation to retrieve the attributes for
* @param classValuesAsString whether to turn Class references into Strings (for
- * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
- * preserve them as Class references
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata}
+ * or to preserve them as Class references
* @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
* {@link AnnotationAttributes} maps (for compatibility with
* {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as
@@ -400,34 +568,7 @@ public abstract class AnnotationUtils {
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
try {
Object value = method.invoke(annotation);
- if (classValuesAsString) {
- if (value instanceof Class) {
- value = ((Class<?>) value).getName();
- }
- else if (value instanceof Class[]) {
- Class<?>[] clazzArray = (Class[]) value;
- String[] newValue = new String[clazzArray.length];
- for (int i = 0; i < clazzArray.length; i++) {
- newValue[i] = clazzArray[i].getName();
- }
- value = newValue;
- }
- }
- if (nestedAnnotationsAsMap && value instanceof Annotation) {
- attrs.put(method.getName(),
- getAnnotationAttributes((Annotation) value, classValuesAsString, true));
- }
- else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
- Annotation[] realAnnotations = (Annotation[]) value;
- AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
- for (int i = 0; i < realAnnotations.length; i++) {
- mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
- }
- attrs.put(method.getName(), mappedAnnotations);
- }
- else {
- attrs.put(method.getName(), value);
- }
+ attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
}
catch (Exception ex) {
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
@@ -438,6 +579,48 @@ public abstract class AnnotationUtils {
}
/**
+ * Adapt the given value according to the given class and nested annotation settings.
+ * @param value the annotation attribute value
+ * @param classValuesAsString whether to turn Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata}
+ * or to preserve them as Class references
+ * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
+ * {@link AnnotationAttributes} maps (for compatibility with
+ * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as
+ * Annotation instances
+ * @return the adapted value, or the original value if no adaptation is needed
+ */
+ static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ if (classValuesAsString) {
+ if (value instanceof Class) {
+ value = ((Class<?>) value).getName();
+ }
+ else if (value instanceof Class[]) {
+ Class<?>[] clazzArray = (Class[]) value;
+ String[] newValue = new String[clazzArray.length];
+ for (int i = 0; i < clazzArray.length; i++) {
+ newValue[i] = clazzArray[i].getName();
+ }
+ value = newValue;
+ }
+ }
+ if (nestedAnnotationsAsMap && value instanceof Annotation) {
+ return getAnnotationAttributes((Annotation) value, classValuesAsString, true);
+ }
+ else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) value;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
+ }
+ return mappedAnnotations;
+ }
+ else {
+ return value;
+ }
+ }
+
+ /**
* Retrieve the <em>value</em> of the {@code &quot;value&quot;} attribute of a
* single-element Annotation, given an annotation instance.
* @param annotation the annotation instance from which to retrieve the value
@@ -449,7 +632,7 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance.
+ * Retrieve the <em>value</em> of a named attribute, given an annotation instance.
* @param annotation the annotation instance from which to retrieve the value
* @param attributeName the name of the attribute value to retrieve
* @return the attribute value, or {@code null} if not found
@@ -478,7 +661,7 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance.
+ * Retrieve the <em>default value</em> of a named attribute, given an annotation instance.
* @param annotation the annotation instance from which to retrieve the default value
* @param attributeName the name of the attribute value to retrieve
* @return the default value of the named attribute, or {@code null} if not found
@@ -500,7 +683,8 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}.
+ * Retrieve the <em>default value</em> of a named attribute, given the
+ * {@link Class annotation type}.
* @param annotationType the <em>annotation type</em> for which the default value should be retrieved
* @param attributeName the name of the attribute value to retrieve.
* @return the default value of the named attribute, or {@code null} if not found
@@ -515,4 +699,68 @@ public abstract class AnnotationUtils {
}
}
+
+ private static void logIntrospectionFailure(AnnotatedElement annotatedElement, Exception ex) {
+ Log loggerToUse = logger;
+ if (loggerToUse == null) {
+ loggerToUse = LogFactory.getLog(AnnotationUtils.class);
+ logger = loggerToUse;
+ }
+ if (loggerToUse.isInfoEnabled()) {
+ loggerToUse.info("Failed to introspect annotations on [" + annotatedElement + "]: " + ex);
+ }
+ }
+
+
+ private static class AnnotationCollector<A extends Annotation> {
+
+ private final Class<? extends Annotation> containerAnnotationType;
+
+ private final Class<A> annotationType;
+
+ private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>();
+
+ private final Set<A> result = new LinkedHashSet<A>();
+
+ public AnnotationCollector(Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+ this.containerAnnotationType = containerAnnotationType;
+ this.annotationType = annotationType;
+ }
+
+ public Set<A> getResult(AnnotatedElement element) {
+ process(element);
+ return Collections.unmodifiableSet(this.result);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void process(AnnotatedElement annotatedElement) {
+ if (this.visited.add(annotatedElement)) {
+ for (Annotation ann : annotatedElement.getAnnotations()) {
+ if (ObjectUtils.nullSafeEquals(this.annotationType, ann.annotationType())) {
+ this.result.add((A) ann);
+ }
+ else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, ann.annotationType())) {
+ this.result.addAll(getValue(ann));
+ }
+ else if (!isInJavaLangAnnotationPackage(ann)) {
+ process(ann.annotationType());
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<A> getValue(Annotation annotation) {
+ try {
+ Method method = annotation.annotationType().getDeclaredMethod("value");
+ ReflectionUtils.makeAccessible(method);
+ return Arrays.asList((A[]) method.invoke(annotation));
+ }
+ catch (Exception ex) {
+ // Unable to read value from repeating annotation container -> ignore it.
+ return Collections.emptyList();
+ }
+ }
+ }
+
}