Support meta-annotation overrides in ASM processing
Prior to this commit, Spring supported meta-annotation attribute overrides in custom composed annotations with reflection-based annotation processing but not with ASM-based annotation processing. This commit ensures that meta-annotation attribute overrides are supported in AnnotationMetadataReadingVisitor.getAnnotationAttributes(). Issue: SPR-11574
This commit is contained in:
@@ -61,7 +61,7 @@ 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>();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* 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.
|
||||
@@ -41,6 +41,7 @@ import org.springframework.util.MultiValueMap;
|
||||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
|
||||
@@ -51,7 +52,13 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
||||
|
||||
protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
|
||||
|
||||
protected final MultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
|
||||
/**
|
||||
* Declared as a {@link LinkedMultiValueMap} instead of {@link MultiValueMap}
|
||||
* in order to ensure that ordering of entries is enforced.
|
||||
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes(LinkedMultiValueMap, String)
|
||||
*/
|
||||
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributeMap = new LinkedMultiValueMap<String, AnnotationAttributes>(
|
||||
4);
|
||||
|
||||
protected final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4);
|
||||
|
||||
@@ -112,8 +119,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
||||
|
||||
@Override
|
||||
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
|
||||
List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
|
||||
AnnotationAttributes raw = (attributes == null ? null : attributes.get(0));
|
||||
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(this.attributeMap,
|
||||
annotationType);
|
||||
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,10 +16,17 @@
|
||||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.asm.Type;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
/**
|
||||
* Internal utility class used when reading annotations.
|
||||
@@ -28,12 +35,13 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
||||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 4.0
|
||||
*/
|
||||
abstract class AnnotationReadingVisitorUtils {
|
||||
|
||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader,
|
||||
AnnotationAttributes original, boolean classValuesAsString) {
|
||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
|
||||
boolean classValuesAsString) {
|
||||
|
||||
if (original == null) {
|
||||
return null;
|
||||
@@ -44,26 +52,24 @@ abstract class AnnotationReadingVisitorUtils {
|
||||
try {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof AnnotationAttributes) {
|
||||
value = convertClassValues(classLoader, (AnnotationAttributes) value,
|
||||
classValuesAsString);
|
||||
value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
|
||||
}
|
||||
else if (value instanceof AnnotationAttributes[]) {
|
||||
AnnotationAttributes[] values = (AnnotationAttributes[])value;
|
||||
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
values[i] = convertClassValues(classLoader, values[i],
|
||||
classValuesAsString);
|
||||
values[i] = convertClassValues(classLoader, values[i], classValuesAsString);
|
||||
}
|
||||
}
|
||||
else if (value instanceof Type) {
|
||||
value = (classValuesAsString ? ((Type) value).getClassName() :
|
||||
classLoader.loadClass(((Type) value).getClassName()));
|
||||
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()));
|
||||
convArray[i] = (classValuesAsString ? array[i].getClassName()
|
||||
: classLoader.loadClass(array[i].getClassName()));
|
||||
}
|
||||
value = convArray;
|
||||
}
|
||||
@@ -83,10 +89,66 @@ abstract class AnnotationReadingVisitorUtils {
|
||||
result.put(entry.getKey(), value);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Class not found - can't resolve class reference in annotation attribute.
|
||||
// Class not found - can't resolve class reference in annotation
|
||||
// attribute.
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the merged attributes of the annotation of the given type, if any,
|
||||
* from the supplied {@code attributeMap}.
|
||||
* <p>Annotation attribute values appearing <em>lower</em> in the annotation
|
||||
* hierarchy (i.e., closer to the declaring class) will override those
|
||||
* defined <em>higher</em> in the annotation hierarchy.
|
||||
* @param attributeMap the map of annotation attribute lists, keyed by
|
||||
* annotation type
|
||||
* @param annotationType the annotation type to look for
|
||||
* @return the merged annotation attributes; or {@code null} if no matching
|
||||
* annotation is present in the {@code attributeMap}
|
||||
* @since 4.0.3
|
||||
*/
|
||||
public static AnnotationAttributes getMergedAnnotationAttributes(
|
||||
LinkedMultiValueMap<String, AnnotationAttributes> attributeMap, String annotationType) {
|
||||
|
||||
// Get the unmerged list of attributes for the target annotation.
|
||||
List<AnnotationAttributes> attributesList = attributeMap.get(annotationType);
|
||||
if (attributesList == null || attributesList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// To start with, we populate the results with all attribute values from the
|
||||
// target annotation.
|
||||
AnnotationAttributes results = attributesList.get(0);
|
||||
Set<String> supportedAttributeNames = results.keySet();
|
||||
|
||||
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
||||
// elements in the map and reverse the order of the keys in order to traverse
|
||||
// "down" the meta-annotation hierarchy.
|
||||
List<String> annotationTypes = new ArrayList<String>(attributeMap.keySet());
|
||||
Collections.reverse(annotationTypes);
|
||||
|
||||
for (String currentAnnotationType : annotationTypes) {
|
||||
if (!currentAnnotationType.startsWith("java.lang.annotation")) {
|
||||
for (String attributeName : supportedAttributeNames) {
|
||||
if (!VALUE.equals(attributeName)) {
|
||||
List<AnnotationAttributes> currentAttributes = attributeMap.get(currentAnnotationType);
|
||||
if (currentAttributes != null && !currentAttributes.isEmpty()) {
|
||||
Object value = currentAttributes.get(0).get(attributeName);
|
||||
if (value != null) {
|
||||
// Overwrite value from target annotation with the value
|
||||
// from an attribute of the same name found lower in the
|
||||
// meta-annotation hierarchy.
|
||||
results.put(attributeName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user