Extend AnnotationMetadata and MethodMetadata

Update AnnotationMetadata and MethodMetadata to extend from a new
AnnotatedTypeMetadata base interface containing the methods that are
common to both. Also introduce new getAllAnnotationAttributes methods
providing MultiValueMap access to both annotation and meta-annotation
attributes.

Existing classreading and standard implementations have been
refactored to support the new interface.
This commit is contained in:
Phillip Webb 2013-02-20 09:38:43 -08:00
parent eb1776e79d
commit 8e445f3a21
11 changed files with 537 additions and 247 deletions

View File

@ -0,0 +1,160 @@
/*
* Copyright 2002-2013 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.type;
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.core.annotation.AnnotationUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Internal utility class used to collect all annotation values including those declared
* on meta-annotations.
*
* @author Phillip Webb
* @since 4.0
*/
class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
String annotationType) {
final Set<String> types = new LinkedHashSet<String>();
process(element, annotationType, new Processor<Object>() {
public Object process(Annotation annotation, int depth) {
if (depth > 0) {
types.add(annotation.annotationType().getName());
}
return null;
}
});
return (types.size() == 0 ? null : types);
}
public static boolean hasMetaAnnotationTypes(AnnotatedElement element,
String annotationType) {
return Boolean.TRUE.equals(
process(element, annotationType, new Processor<Boolean>() {
public Boolean process(Annotation annotation, int depth) {
if (depth > 0) {
return true;
}
return null;
}
}));
}
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
return Boolean.TRUE.equals(
process(element, annotationType, new Processor<Boolean>() {
public Boolean process(Annotation annotation, int depth) {
return true;
}
}));
}
public static Map<String, Object> getAnnotationAttributes(AnnotatedElement element,
String annotationType, final boolean classValuesAsString,
final boolean nestedAnnotationsAsMap) {
return process(element, annotationType, new Processor<Map<String, Object>>() {
public Map<String, Object> process(Annotation annotation, int depth) {
return AnnotationUtils.getAnnotationAttributes(annotation,
classValuesAsString, nestedAnnotationsAsMap);
}
});
}
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<Object>() {
public Object process(Annotation annotation, int depth) {
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;
}
});
return (attributes.size() == 0 ? null : attributes);
}
/**
* Process all annotations of the specified annotation type and recursively all
* meta-annotations on the specified type.
* @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
* @return the result of the processor
*/
private static <T> T process(AnnotatedElement element, String annotationType,
Processor<T> processor) {
return recursivelyProcess(element, annotationType, processor,
new HashSet<AnnotatedElement>(), 0);
}
private static <T> T recursivelyProcess(AnnotatedElement element,
String annotationType, Processor<T> processor, Set<AnnotatedElement> visited,
int depth) {
T result = null;
if (visited.add(element)) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationType) || depth > 0) {
result = result != null ? result :
processor.process(annotation, depth);
result = result != null ? result :
recursivelyProcess(annotation.annotationType(), annotationType,
processor, visited, depth + 1);
}
}
for (Annotation annotation : element.getAnnotations()) {
result = result != null ? result :
recursivelyProcess(annotation.annotationType(), annotationType,
processor, visited, depth);
}
}
return result;
}
/**
* Callback interface used to process an annotation.
* @param <T> the result type
*/
private static interface Processor<T> {
/**
* Called to process the annotation.
* @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
* @return the result of the processing or {@code null} to continue
*/
T process(Annotation annotation, int depth);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2002-2013 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.type;
import java.util.Map;
import org.springframework.util.MultiValueMap;
/**
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
* or {@link MethodMetadata method}), in a form that does not necessarily require the
* class-loading.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @since 4.0
* @see AnnotationMetadata
* @see MethodMetadata
*/
public interface AnnotatedTypeMetadata {
/**
* Determine whether the underlying type has an annotation or
* meta-annotation of the given type defined.
* <p>If this method returns {@code true}, then
* {@link #getAnnotationAttributes} will return a non-null Map.
* @param annotationType the annotation type to look for
* @return whether a matching annotation is defined
*/
boolean isAnnotated(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @param classValuesAsString whether to convert class references to String
* class names for exposure as values in the returned Map, instead of Class
* references which might potentially have to be loaded first
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying method, as direct annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and
* a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying method, as direct annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @param classValuesAsString whether to convert class references to String
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and
* a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
* @see #getAllAnnotationAttributes(String)
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType,
boolean classValuesAsString);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@ -16,7 +16,6 @@
package org.springframework.core.type;
import java.util.Map;
import java.util.Set;
/**
@ -25,11 +24,13 @@ import java.util.Set;
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Phillip Webb
* @since 2.5
* @see StandardAnnotationMetadata
* @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata()
* @see AnnotatedTypeMetadata
*/
public interface AnnotationMetadata extends ClassMetadata {
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {
/**
* Return the names of all annotation types defined on the underlying class.
@ -61,42 +62,6 @@ public interface AnnotationMetadata extends ClassMetadata {
*/
boolean hasMetaAnnotation(String metaAnnotationType);
/**
* Determine whether the underlying class has an annotation or
* meta-annotation of the given type defined.
* <p>This is equivalent to a "hasAnnotation || hasMetaAnnotation"
* check. If this method returns {@code true}, then
* {@link #getAnnotationAttributes} will return a non-null Map.
* @param annotationType the annotation type to look for
* @return whether a matching annotation is defined
*/
boolean isAnnotated(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying class, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @param classValuesAsString whether to convert class references to String
* class names for exposure as values in the returned Map, instead of Class
* references which might potentially have to be loaded first
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
/**
* Determine whether the underlying class has any methods that are
* annotated (or meta-annotated) with the given annotation type.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@ -16,8 +16,6 @@
package org.springframework.core.type;
import java.util.Map;
/**
* Interface that defines abstract access to the annotations of a specific
* class, in a form that does not require that class to be loaded yet.
@ -25,11 +23,13 @@ import java.util.Map;
* @author Juergen Hoeller
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @since 3.0
* @see StandardMethodMetadata
* @see AnnotationMetadata#getAnnotatedMethods
* @see AnnotatedTypeMetadata
*/
public interface MethodMetadata {
public interface MethodMetadata extends AnnotatedTypeMetadata {
/**
* Return the name of the method.
@ -57,23 +57,4 @@ public interface MethodMetadata {
*/
boolean isOverridable();
/**
* Determine whether the underlying method has an annotation or
* meta-annotation of the given type defined.
* @param annotationType the annotation type to look for
* @return whether a matching annotation is defined
*/
boolean isAnnotated(String annotationType);
/**
* Retrieve the attributes of the annotation of the given type,
* if any (i.e. if defined on the underlying method, as direct
* annotation or as meta-annotation).
* @param annotationType the annotation type to look for
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationType);
}

View File

@ -23,7 +23,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.MultiValueMap;
/**
* {@link AnnotationMetadata} implementation that uses standard reflection
@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils;
* @author Juergen Hoeller
* @author Mark Fisher
* @author Chris Beams
* @author Phillip Webb
* @since 2.5
*/
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
@ -74,21 +75,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
public Set<String> getMetaAnnotationTypes(String annotationType) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
Set<String> types = new LinkedHashSet<String>();
Annotation[] metaAnns = ann.annotationType().getAnnotations();
for (Annotation metaAnn : metaAnns) {
types.add(metaAnn.annotationType().getName());
for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) {
types.add(metaMetaAnn.annotationType().getName());
}
}
return types;
}
}
return null;
return AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationType);
}
public boolean hasAnnotation(String annotationType) {
@ -102,76 +89,39 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
public boolean hasMetaAnnotation(String annotationType) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
Annotation[] metaAnns = ann.annotationType().getAnnotations();
for (Annotation metaAnn : metaAnns) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return true;
}
for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) {
if (metaMetaAnn.annotationType().getName().equals(annotationType)) {
return true;
}
}
}
}
return false;
return AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationType);
}
public boolean isAnnotated(String annotationType) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
return true;
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return true;
}
}
}
return false;
return AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationType);
}
public Map<String, Object> getAnnotationAttributes(String annotationType) {
return this.getAnnotationAttributes(annotationType, false);
}
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(
ann, classValuesAsString, this.nestedAnnotationsAsMap);
public Map<String, Object> getAnnotationAttributes(String annotationType,
boolean classValuesAsString) {
return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(),
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
return getAllAnnotationAttributes(annotationType, false);
}
for (Annotation ann : anns) {
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(
metaAnn, classValuesAsString, this.nestedAnnotationsAsMap);
}
}
}
return null;
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(),
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
public boolean hasAnnotatedMethods(String annotationType) {
Method[] methods = getIntrospectedClass().getDeclaredMethods();
for (Method method : methods) {
for (Annotation ann : method.getAnnotations()) {
if (ann.annotationType().getName().equals(annotationType)) {
if (AnnotatedElementUtils.isAnnotated(method, annotationType)) {
return true;
}
else {
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return true;
}
}
}
}
}
return false;
}
@ -180,19 +130,9 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
Method[] methods = getIntrospectedClass().getDeclaredMethods();
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
for (Method method : methods) {
for (Annotation ann : method.getAnnotations()) {
if (ann.annotationType().getName().equals(annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break;
}
else {
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
break;
}
}
}
if (AnnotatedElementUtils.isAnnotated(method, annotationType)) {
annotatedMethods.add(new StandardMethodMetadata(method,
this.nestedAnnotationsAsMap));
}
}
return annotatedMethods;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@ -16,13 +16,12 @@
package org.springframework.core.type;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
/**
* {@link MethodMetadata} implementation that uses standard reflection
@ -31,6 +30,7 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @since 3.0
*/
public class StandardMethodMetadata implements MethodMetadata {
@ -89,35 +89,26 @@ public class StandardMethodMetadata implements MethodMetadata {
}
public boolean isAnnotated(String annotationType) {
Annotation[] anns = this.introspectedMethod.getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
return true;
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return true;
}
}
}
return false;
return AnnotatedElementUtils.isAnnotated(this.introspectedMethod, annotationType);
}
public Map<String, Object> getAnnotationAttributes(String annotationType) {
Annotation[] anns = this.introspectedMethod.getAnnotations();
for (Annotation ann : anns) {
if (ann.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(
ann, true, nestedAnnotationsAsMap);
return getAnnotationAttributes(annotationType, false);
}
for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
if (metaAnn.annotationType().getName().equals(annotationType)) {
return AnnotationUtils.getAnnotationAttributes(
metaAnn, true, this.nestedAnnotationsAsMap);
public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAnnotationAttributes(this.introspectedMethod,
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
return getAllAnnotationAttributes(annotationType, false);
}
}
return null;
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
}

View File

@ -34,13 +34,14 @@ import org.springframework.asm.SpringAsmInfo;
import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1.1
*/
abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
@ -51,12 +52,14 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
protected final ClassLoader classLoader;
public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) {
super(SpringAsmInfo.ASM_VERSION);
this.classLoader = classLoader;
this.attributes = attributes;
}
public void visit(String attributeName, Object attributeValue) {
this.attributes.put(attributeName, attributeValue);
}
@ -108,12 +111,14 @@ final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationV
private final List<AnnotationAttributes> allNestedAttributes = new ArrayList<AnnotationAttributes>();
public RecursiveAnnotationArrayVisitor(
String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) {
super(classLoader, attributes);
this.attributeName = attributeName;
}
@Override
public void visit(String attributeName, Object attributeValue) {
Object newValue = attributeValue;
@ -155,12 +160,14 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
private final String annotationType;
public RecursiveAnnotationAttributesVisitor(
String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) {
super(classLoader, attributes);
this.annotationType = annotationType;
}
public final void visitEnd() {
try {
Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
@ -214,18 +221,20 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @since 3.0
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
private final String annotationType;
private final Map<String, AnnotationAttributes> attributesMap;
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
public AnnotationAttributesReadingVisitor(
String annotationType, Map<String, AnnotationAttributes> attributesMap,
String annotationType, MultiValueMap<String, AnnotationAttributes> attributesMap,
Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) {
super(annotationType, new AnnotationAttributes(), classLoader);
@ -234,29 +243,33 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
public void doVisitEnd(Class<?> annotationClass) {
super.doVisitEnd(annotationClass);
this.attributesMap.put(this.annotationType, this.attributes);
registerMetaAnnotations(annotationClass);
List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
if (attributes == null) {
this.attributesMap.add(this.annotationType, this.attributes);
}
else {
attributes.add(0, this.attributes);
}
private void registerMetaAnnotations(Class<?> annotationClass) {
// Register annotations that the annotation type is annotated with.
Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
this.attributesMap.put(metaAnnotation.annotationType().getName(),
AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true));
}
for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
}
recusivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation);
}
if (this.metaAnnotationMap != null) {
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
private void recusivelyCollectMetaAnnotations(Set<String> visited, Annotation annotation) {
if (visited.add(annotation.annotationType().getName())) {
this.attributesMap.add(annotation.annotationType().getName(),
AnnotationUtils.getAnnotationAttributes(annotation, true, true));
for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) {
recusivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
}
}
}
}

View File

@ -41,6 +41,7 @@ import org.springframework.util.MultiValueMap;
* @author Juergen Hoeller
* @author Mark Fisher
* @author Costin Leau
* @author Phillip Webb
* @since 2.5
*/
public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
@ -51,7 +52,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
protected final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(4);
protected final MultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
protected final MultiValueMap<String, MethodMetadata> methodMetadataMap = new LinkedMultiValueMap<String, MethodMetadata>();
@ -105,60 +106,30 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
}
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
AnnotationAttributes raw = this.attributeMap.get(annotationType);
return convertClassValues(raw, classValuesAsString);
List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
AnnotationAttributes raw = (attributes == null ? null : attributes.get(0));
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw,
classValuesAsString);
}
private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) {
if (original == null) {
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
return getAllAnnotationAttributes(annotationType, false);
}
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
if (attributes == null) {
return null;
}
AnnotationAttributes result = new AnnotationAttributes(original.size());
for (Map.Entry<String, Object> entry : original.entrySet()) {
try {
Object value = entry.getValue();
if (value instanceof AnnotationAttributes) {
value = convertClassValues((AnnotationAttributes) value, classValuesAsString);
}
else if (value instanceof AnnotationAttributes[]) {
AnnotationAttributes[] values = (AnnotationAttributes[])value;
for (int i = 0; i < values.length; i++) {
values[i] = convertClassValues(values[i], classValuesAsString);
for (AnnotationAttributes raw : attributes) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
this.classLoader, raw, classValuesAsString).entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
this.classLoader.loadClass(((Type) value).getClassName()));
}
else if (value instanceof Type[]) {
Type[] array = (Type[]) value;
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]);
for (int i = 0; i < array.length; i++) {
convArray[i] = (classValuesAsString ? array[i].getClassName() :
this.classLoader.loadClass(array[i].getClassName()));
}
value = convArray;
}
else 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;
}
}
result.put(entry.getKey(), value);
}
catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute.
}
}
return result;
return allAttributes;
}
public boolean hasAnnotatedMethods(String annotationType) {

View File

@ -0,0 +1,92 @@
/*
* Copyright 2002-2013 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.type.classreading;
import java.util.Map;
import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
/**
* Internal utility class used when reading annotations.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Costin Leau
* @author Phillip Webb
* @since 4.0
*/
abstract class AnnotationReadingVisitorUtils {
public static AnnotationAttributes convertClassValues(ClassLoader classLoader,
AnnotationAttributes original, boolean classValuesAsString) {
if (original == null) {
return null;
}
AnnotationAttributes result = new AnnotationAttributes(original.size());
for (Map.Entry<String, Object> entry : original.entrySet()) {
try {
Object value = entry.getValue();
if (value instanceof AnnotationAttributes) {
value = convertClassValues(classLoader, (AnnotationAttributes) value,
classValuesAsString);
}
else if (value instanceof AnnotationAttributes[]) {
AnnotationAttributes[] values = (AnnotationAttributes[])value;
for (int i = 0; i < values.length; i++) {
values[i] = convertClassValues(classLoader, values[i],
classValuesAsString);
}
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
classLoader.loadClass(((Type) value).getClassName()));
}
else if (value instanceof Type[]) {
Type[] array = (Type[]) value;
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]);
for (int i = 0; i < array.length; i++) {
convArray[i] = (classValuesAsString ? array[i].getClassName() :
classLoader.loadClass(array[i].getClassName()));
}
value = convArray;
}
else 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;
}
}
result.put(entry.getKey(), value);
}
catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute.
}
}
return result;
}
}

View File

@ -16,7 +16,7 @@
package org.springframework.core.type.classreading;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.asm.AnnotationVisitor;
@ -26,6 +26,7 @@ import org.springframework.asm.SpringAsmInfo;
import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
@ -37,6 +38,7 @@ import org.springframework.util.MultiValueMap;
* @author Mark Pollack
* @author Costin Leau
* @author Chris Beams
* @author Phillip Webb
* @since 3.0
*/
public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata {
@ -51,7 +53,7 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
protected final MultiValueMap<String, MethodMetadata> methodMetadataMap;
protected final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(2);
protected final MultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(2);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
@ -93,8 +95,34 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
return this.attributeMap.containsKey(annotationType);
}
public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return this.attributeMap.get(annotationType);
public Map<String, Object> getAnnotationAttributes(String annotationType) {
return getAnnotationAttributes(annotationType, false);
}
public Map<String, Object> getAnnotationAttributes(String annotationType,
boolean classValuesAsString) {
List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
return (attributes == null ? null : AnnotationReadingVisitorUtils.convertClassValues(
this.classLoader, attributes.get(0), classValuesAsString));
}
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
return getAllAnnotationAttributes(annotationType, false);
}
public MultiValueMap<String, Object> getAllAnnotationAttributes(
String annotationType, boolean classValuesAsString) {
if(!this.attributeMap.containsKey(annotationType)) {
return null;
}
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
for (AnnotationAttributes annotationAttributes : this.attributeMap.get(annotationType)) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
return allAttributes;
}
public String getDeclaringClassName() {

View File

@ -18,7 +18,9 @@ package org.springframework.core.type;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -29,10 +31,12 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@ -45,6 +49,7 @@ import org.springframework.stereotype.Component;
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
*/
public class AnnotationMetadataTests {
@ -93,7 +98,7 @@ public class AnnotationMetadataTests {
assertThat(metadata.hasAnnotation(Component.class.getName()), is(true));
assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true));
assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true));
assertThat(metadata.getAnnotationTypes().size(), is(3));
assertThat(metadata.getAnnotationTypes().size(), is(5));
assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true));
assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true));
assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true));
@ -104,6 +109,15 @@ public class AnnotationMetadataTests {
AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName());
assertThat(scopeAttrs.size(), is(1));
assertThat(scopeAttrs.getString("value"), is("myScope"));
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
MethodMetadata method = methods.iterator().next();
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
assertTrue(metadata.isAnnotated(IsAnnotatedAnnotation.class.getName()));
{ // perform tests with classValuesAsString = false (the default)
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName());
assertThat(specialAttrs.size(), is(6));
@ -137,6 +151,10 @@ public class AnnotationMetadataTests {
assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT));
assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray"));
assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray"));
assertEquals("direct", metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
}
{ // perform tests with classValuesAsString = true
AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true);
@ -161,6 +179,10 @@ public class AnnotationMetadataTests {
AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray");
assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray"));
assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray"));
assertEquals(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"), "direct");
allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
}
}
@ -201,6 +223,29 @@ public class AnnotationMetadataTests {
NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)};
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectAnnotation {
String value();
}
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface IsAnnotatedAnnotation {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@DirectAnnotation("meta")
@IsAnnotatedAnnotation
public @interface MetaAnnotation {
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@MetaAnnotation
public @interface MetaMetaAnnotation {
}
@Component("myName")
@Scope("myScope")
@ -211,6 +256,8 @@ public class AnnotationMetadataTests {
@NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class})
})
@SuppressWarnings({"serial", "unused"})
@DirectAnnotation("direct")
@MetaMetaAnnotation
private static class AnnotatedComponent implements Serializable {
@TestAutowired
@ -219,6 +266,11 @@ public class AnnotationMetadataTests {
public void doSleep() {
}
@DirectAnnotation("direct")
@MetaMetaAnnotation
public void meta() {
}
}
}