|
|
|
@ -25,8 +25,8 @@ import java.lang.reflect.Method;
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -2163,4 +2143,14 @@ public abstract class AnnotationUtils {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static class DefaultValueHolder { |
|
|
|
|
|
|
|
|
|
final Object defaultValue; |
|
|
|
|
|
|
|
|
|
public DefaultValueHolder(Object defaultValue) { |
|
|
|
|
this.defaultValue = defaultValue; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|