Avoid annotation synthesizing for getAnnotationAttributes retrieval
Issue: SPR-13621
This commit is contained in:
@@ -977,7 +977,7 @@ public class AnnotatedElementUtils {
|
||||
* annotation attributes from lower levels in the annotation hierarchy
|
||||
* during the {@link #postProcess} phase.
|
||||
* @since 4.2
|
||||
* @see AnnotationUtils#getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean, boolean)
|
||||
* @see AnnotationUtils#retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
* @see AnnotationUtils#postProcessAnnotationAttributes
|
||||
*/
|
||||
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||
@@ -1003,8 +1003,8 @@ public class AnnotatedElementUtils {
|
||||
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = (this.annotationType != null ? annotation.annotationType() == this.annotationType :
|
||||
annotation.annotationType().getName().equals(this.annotationName));
|
||||
return (found ? AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap, true) : null);
|
||||
return (found ? AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -112,12 +112,6 @@ public abstract class AnnotationUtils {
|
||||
public static final String VALUE = "value";
|
||||
|
||||
|
||||
/**
|
||||
* An object that can be stored in {@link AnnotationAttributes} as a
|
||||
* placeholder for an attribute's declared default value.
|
||||
*/
|
||||
private static final Object DEFAULT_VALUE_PLACEHOLDER = new String("<SPRING DEFAULT VALUE PLACEHOLDER>");
|
||||
|
||||
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
||||
|
||||
@@ -1002,7 +996,10 @@ public abstract class AnnotationUtils {
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap, false);
|
||||
AnnotationAttributes attributes =
|
||||
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1010,16 +1007,9 @@ public abstract class AnnotationUtils {
|
||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||
* <p><strong>NOTE</strong>: This variant of {@code getAnnotationAttributes()} is
|
||||
* only intended for use within the framework. Specifically, the {@code mergeMode} flag
|
||||
* can be set to {@code true} in order to support processing of attribute aliases while
|
||||
* merging attributes within an annotation hierarchy. When running in <em>merge mode</em>,
|
||||
* the following special rules apply:
|
||||
* only intended for use within the framework. The following special rules apply:
|
||||
* <ol>
|
||||
* <li>The supplied annotation will <em>not</em> be
|
||||
* {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes;
|
||||
* however, nested annotations and arrays of nested annotations <em>will</em> be
|
||||
* synthesized.</li>
|
||||
* <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li>
|
||||
* <li>Default values will be replaced with default value placeholders.</li>
|
||||
* <li>The resulting, merged annotation attributes should eventually be
|
||||
* {@linkplain #postProcessAnnotationAttributes post-processed} in order to
|
||||
* ensure that placeholders have been replaced by actual default values and
|
||||
@@ -1035,33 +1025,26 @@ public abstract class AnnotationUtils {
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @param mergeMode whether the annotation attributes should be created
|
||||
* using <em>merge mode</em>
|
||||
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||
* and corresponding attribute values as values (never {@code null})
|
||||
* @since 4.2
|
||||
* @see #postProcessAnnotationAttributes
|
||||
*/
|
||||
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap, boolean mergeMode) {
|
||||
|
||||
if (!mergeMode) {
|
||||
annotation = synthesizeAnnotation(annotation, annotatedElement);
|
||||
}
|
||||
static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
AnnotationAttributes attrs = new AnnotationAttributes(annotationType);
|
||||
AnnotationAttributes attributes = new AnnotationAttributes(annotationType);
|
||||
|
||||
for (Method method : getAttributeMethods(annotationType)) {
|
||||
try {
|
||||
Object value = method.invoke(annotation);
|
||||
Object attributeValue = method.invoke(annotation);
|
||||
Object defaultValue = method.getDefaultValue();
|
||||
if (mergeMode && defaultValue != null) {
|
||||
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||
value = DEFAULT_VALUE_PLACEHOLDER;
|
||||
}
|
||||
if (defaultValue != null && ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
||||
attributeValue = new DefaultValueHolder(defaultValue);
|
||||
}
|
||||
attrs.put(method.getName(),
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
attributes.put(method.getName(),
|
||||
adaptValue(annotatedElement, attributeValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException) {
|
||||
@@ -1071,7 +1054,8 @@ public abstract class AnnotationUtils {
|
||||
throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1109,7 +1093,6 @@ public abstract class AnnotationUtils {
|
||||
|
||||
if (value instanceof Annotation) {
|
||||
Annotation annotation = (Annotation) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, true);
|
||||
}
|
||||
@@ -1120,12 +1103,11 @@ public abstract class AnnotationUtils {
|
||||
|
||||
if (value instanceof Annotation[]) {
|
||||
Annotation[] annotations = (Annotation[]) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length];
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
mappedAnnotations[i] = getAnnotationAttributes(annotatedElement, annotations[i],
|
||||
classValuesAsString, true);
|
||||
mappedAnnotations[i] =
|
||||
getAnnotationAttributes(annotatedElement, annotations[i], classValuesAsString, true);
|
||||
}
|
||||
return mappedAnnotations;
|
||||
}
|
||||
@@ -1138,6 +1120,101 @@ public abstract class AnnotationUtils {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the supplied {@link AnnotationAttributes}.
|
||||
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
|
||||
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
|
||||
* and replaces default value placeholders with their original default values.
|
||||
* @param annotatedElement the element that is annotated with an annotation or
|
||||
* annotation hierarchy from which the supplied attributes were created;
|
||||
* may be {@code null} if unknown
|
||||
* @param attributes the annotation attributes to post-process
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested annotations into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @since 4.2
|
||||
* @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
* @see #getDefaultValue(Class, String)
|
||||
*/
|
||||
static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
// Abort?
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
|
||||
// Track which attribute values have already been replaced so that we can short
|
||||
// circuit the search algorithms.
|
||||
Set<String> valuesAlreadyReplaced = new HashSet<String>();
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
|
||||
|
||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
|
||||
|
||||
// Something to validate or replace with an alias?
|
||||
if (valuePresent || aliasPresent) {
|
||||
if (valuePresent && aliasPresent) {
|
||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||
String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
|
||||
throw new AnnotationConfigurationException(String.format(
|
||||
"In AnnotationAttributes for annotation [%s] declared on %s, " +
|
||||
"attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
|
||||
"but only one is permitted.", annotationType.getName(), elementAsString,
|
||||
attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
|
||||
ObjectUtils.nullSafeToString(aliasedValue)));
|
||||
}
|
||||
}
|
||||
else if (aliasPresent) {
|
||||
// Replace value with aliasedValue
|
||||
attributes.put(attributeName,
|
||||
adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(attributeName);
|
||||
}
|
||||
else {
|
||||
// Replace aliasedValue with value
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any remaining placeholders with actual default values
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
if (value instanceof DefaultValueHolder) {
|
||||
value = ((DefaultValueHolder) value).defaultValue;
|
||||
attributes.put(attributeName,
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the <em>value</em> of the {@code value} attribute of a
|
||||
* single-element Annotation, given an annotation instance.
|
||||
@@ -1436,7 +1513,7 @@ public abstract class AnnotationUtils {
|
||||
return map;
|
||||
}
|
||||
|
||||
map = new HashMap<String, List<String>>();
|
||||
map = new LinkedHashMap<String, List<String>>();
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
List<String> aliasNames = getAttributeAliasNames(attribute);
|
||||
if (!aliasNames.isEmpty()) {
|
||||
@@ -1609,103 +1686,6 @@ public abstract class AnnotationUtils {
|
||||
return (method != null && method.getName().equals("annotationType") && method.getParameterTypes().length == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the supplied {@link AnnotationAttributes}.
|
||||
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
|
||||
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
|
||||
* and replaces {@linkplain #DEFAULT_VALUE_PLACEHOLDER placeholders} with their
|
||||
* original default values.
|
||||
* @param element the element that is annotated with an annotation or
|
||||
* annotation hierarchy from which the supplied attributes were created;
|
||||
* may be {@code null} if unknown
|
||||
* @param attributes the annotation attributes to post-process
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested annotations into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @since 4.2
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean, boolean)
|
||||
* @see #DEFAULT_VALUE_PLACEHOLDER
|
||||
* @see #getDefaultValue(Class, String)
|
||||
*/
|
||||
static void postProcessAnnotationAttributes(AnnotatedElement element, AnnotationAttributes attributes,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
// Abort?
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
|
||||
// Track which attribute values have already been replaced so that we can short
|
||||
// circuit the search algorithms.
|
||||
Set<String> valuesAlreadyReplaced = new HashSet<String>();
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
boolean valuePresent = (value != null && value != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
boolean aliasPresent = (aliasedValue != null && aliasedValue != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
// Something to validate or replace with an alias?
|
||||
if (valuePresent || aliasPresent) {
|
||||
if (valuePresent && aliasPresent) {
|
||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||
String elementAsString = (element != null ? element.toString() : "unknown element");
|
||||
String msg = String.format("In AnnotationAttributes for annotation [%s] declared on [%s], " +
|
||||
"attribute [%s] and its alias [%s] are declared with values of [%s] and [%s], " +
|
||||
"but only one declaration is permitted.", annotationType.getName(),
|
||||
elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
else if (aliasPresent) {
|
||||
// Replace value with aliasedValue
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(attributeName);
|
||||
}
|
||||
else {
|
||||
// Replace aliasedValue with value
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any remaining placeholders with actual default values
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, getDefaultValue(annotationType, attributeName), classValuesAsString,
|
||||
nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>If the supplied throwable is an {@link AnnotationConfigurationException},
|
||||
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
|
||||
@@ -2163,4 +2143,14 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultValueHolder {
|
||||
|
||||
final Object defaultValue;
|
||||
|
||||
public DefaultValueHolder(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user