diff options
Diffstat (limited to 'spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java')
-rw-r--r-- | spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java | 293 |
1 files changed, 196 insertions, 97 deletions
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 09eb167e..2fe059a5 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 @@ -22,6 +22,7 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collections; @@ -111,6 +112,7 @@ public abstract class AnnotationUtils { */ public static final String VALUE = "value"; + private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable"; private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache = new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256); @@ -308,6 +310,7 @@ public abstract class AnnotationUtils { * @since 4.2 * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType @@ -343,6 +346,7 @@ public abstract class AnnotationUtils { * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType @@ -388,6 +392,7 @@ public abstract class AnnotationUtils { * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType @@ -423,6 +428,7 @@ public abstract class AnnotationUtils { * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class) + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType @@ -616,7 +622,7 @@ public abstract class AnnotationUtils { static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) { Boolean found = annotatedInterfaceCache.get(iface); if (found != null) { - return found.booleanValue(); + return found; } found = Boolean.FALSE; for (Method ifcMethod : iface.getMethods()) { @@ -631,7 +637,7 @@ public abstract class AnnotationUtils { } } annotatedInterfaceCache.put(iface, found); - return found.booleanValue(); + return found; } /** @@ -883,14 +889,14 @@ public abstract class AnnotationUtils { AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType); Boolean metaPresent = metaPresentCache.get(cacheKey); if (metaPresent != null) { - return metaPresent.booleanValue(); + return metaPresent; } metaPresent = Boolean.FALSE; if (findAnnotation(annotationType, metaAnnotationType, false) != null) { metaPresent = Boolean.TRUE; } metaPresentCache.put(cacheKey, metaPresent); - return metaPresent.booleanValue(); + return metaPresent; } /** @@ -1013,6 +1019,13 @@ public abstract class AnnotationUtils { public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + return getAnnotationAttributes( + (Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap); + } + + private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement, + Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + AnnotationAttributes attributes = retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap); postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap); @@ -1047,7 +1060,7 @@ public abstract class AnnotationUtils { * @since 4.2 * @see #postProcessAnnotationAttributes */ - static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation, + static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { Class<? extends Annotation> annotationType = annotation.annotationType(); @@ -1091,14 +1104,14 @@ public abstract class AnnotationUtils { * {@code Annotation} instances * @return the adapted value, or the original value if no adaptation is needed */ - static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString, + static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { if (classValuesAsString) { - if (value instanceof Class) { + if (value instanceof Class<?>) { return ((Class<?>) value).getName(); } - else if (value instanceof Class[]) { + else if (value instanceof Class<?>[]) { Class<?>[] clazzArray = (Class<?>[]) value; String[] classNames = new String[clazzArray.length]; for (int i = 0; i < clazzArray.length; i++) { @@ -1138,6 +1151,63 @@ public abstract class AnnotationUtils { } /** + * Register the annotation-declared default values for the given attributes, + * if available. + * @param attributes the annotation attributes to process + * @since 4.3.2 + */ + public static void registerDefaultValues(AnnotationAttributes attributes) { + // Only do defaults scanning for public annotations; we'd run into + // IllegalAccessExceptions otherwise, and we don't want to mess with + // accessibility in a SecurityManager environment. + Class<? extends Annotation> annotationType = attributes.annotationType(); + if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) { + // Check declared default values of attributes in the annotation type. + for (Method annotationAttribute : getAttributeMethods(annotationType)) { + String attributeName = annotationAttribute.getName(); + Object defaultValue = annotationAttribute.getDefaultValue(); + if (defaultValue != null && !attributes.containsKey(attributeName)) { + if (defaultValue instanceof Annotation) { + defaultValue = getAnnotationAttributes((Annotation) defaultValue, false, true); + } + else if (defaultValue instanceof Annotation[]) { + Annotation[] realAnnotations = (Annotation[]) defaultValue; + AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; + for (int i = 0; i < realAnnotations.length; i++) { + mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], false, true); + } + defaultValue = mappedAnnotations; + } + attributes.put(attributeName, new DefaultValueHolder(defaultValue)); + } + } + } + } + + /** + * Post-process the supplied {@link AnnotationAttributes}, preserving nested + * annotations as {@code Annotation} instances. + * <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 + * @since 4.3.2 + * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean) + * @see #getDefaultValue(Class, String) + */ + public static void postProcessAnnotationAttributes(Object annotatedElement, + AnnotationAttributes attributes, boolean classValuesAsString) { + + postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false); + } + + /** * 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} @@ -1154,10 +1224,10 @@ public abstract class AnnotationUtils { * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @since 4.2 - * @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) + * @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean) * @see #getDefaultValue(Class, String) */ - static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement, + static void postProcessAnnotationAttributes(Object annotatedElement, AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { // Abort? @@ -1171,51 +1241,55 @@ public abstract class AnnotationUtils { // 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)) { + if (!attributes.validated) { + // 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)); - 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))); - } + for (String aliasedAttributeName : aliasMap.get(attributeName)) { + if (valuesAlreadyReplaced.contains(aliasedAttributeName)) { + continue; } - 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); + + 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); + } } } } + attributes.validated = true; } // Replace any remaining placeholders with actual default values @@ -1355,8 +1429,12 @@ public abstract class AnnotationUtils { * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) * @see #synthesizeAnnotation(Class) */ - @SuppressWarnings("unchecked") public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) { + return synthesizeAnnotation(annotation, (Object) annotatedElement); + } + + @SuppressWarnings("unchecked") + static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) { if (annotation == null) { return null; } @@ -1461,7 +1539,7 @@ public abstract class AnnotationUtils { * @see #synthesizeAnnotation(Annotation, AnnotatedElement) * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) */ - public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) { + static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) { if (annotations == null) { return null; } @@ -1489,7 +1567,7 @@ public abstract class AnnotationUtils { * {@code @AliasFor} is detected * @since 4.2.1 * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) - * @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement) + * @see #synthesizeAnnotationArray(Annotation[], Object) */ @SuppressWarnings("unchecked") static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) { @@ -1577,7 +1655,7 @@ public abstract class AnnotationUtils { private static boolean isSynthesizable(Class<? extends Annotation> annotationType) { Boolean synthesizable = synthesizableCache.get(annotationType); if (synthesizable != null) { - return synthesizable.booleanValue(); + return synthesizable; } synthesizable = Boolean.FALSE; @@ -1605,7 +1683,7 @@ public abstract class AnnotationUtils { } synthesizableCache.put(annotationType, synthesizable); - return synthesizable.booleanValue(); + return synthesizable; } /** @@ -1721,6 +1799,29 @@ public abstract class AnnotationUtils { } /** + * Resolve the container type for the supplied repeatable {@code annotationType}. + * <p>Automatically detects a <em>container annotation</em> declared via + * {@link java.lang.annotation.Repeatable}. If the supplied annotation type + * is not annotated with {@code @Repeatable}, this method simply returns + * {@code null}. + * @since 4.2 + */ + @SuppressWarnings("unchecked") + static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) { + try { + Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME); + if (repeatable != null) { + Object value = getValue(repeatable); + return (Class<? extends Annotation>) value; + } + } + catch (Exception ex) { + handleIntrospectionFailure(annotationType, ex); + } + return null; + } + + /** * If the supplied throwable is an {@link AnnotationConfigurationException}, * it will be cast to an {@code AnnotationConfigurationException} and thrown, * allowing it to propagate to the caller. @@ -1774,7 +1875,7 @@ public abstract class AnnotationUtils { /** * Cache key for the AnnotatedElement cache. */ - private static class AnnotationCacheKey { + private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> { private final AnnotatedElement element; @@ -1801,13 +1902,25 @@ public abstract class AnnotationUtils { public int hashCode() { return (this.element.hashCode() * 29 + this.annotationType.hashCode()); } + + @Override + public String toString() { + return "@" + this.annotationType + " on " + this.element; + } + + @Override + public int compareTo(AnnotationCacheKey other) { + int result = this.element.toString().compareTo(other.element.toString()); + if (result == 0) { + result = this.annotationType.getName().compareTo(other.annotationType.getName()); + } + return result; + } } private static class AnnotationCollector<A extends Annotation> { - private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable"; - private final Class<A> annotationType; private final Class<? extends Annotation> containerAnnotationType; @@ -1825,21 +1938,6 @@ public abstract class AnnotationUtils { this.declaredMode = declaredMode; } - @SuppressWarnings("unchecked") - static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) { - try { - Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME); - if (repeatable != null) { - Object value = AnnotationUtils.getValue(repeatable); - return (Class<? extends Annotation>) value; - } - } - catch (Exception ex) { - handleIntrospectionFailure(annotationType, ex); - } - return null; - } - Set<A> getResult(AnnotatedElement element) { process(element); return Collections.unmodifiableSet(this.result); @@ -1951,12 +2049,19 @@ public abstract class AnnotationUtils { this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ? this.sourceAnnotationType : aliasFor.annotation()); this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute); + if (this.aliasedAnnotationType == this.sourceAnnotationType && + this.aliasedAttributeName.equals(this.sourceAttributeName)) { + String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " + + "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.", + sourceAttribute.getName(), declaringClass.getName()); + throw new AnnotationConfigurationException(msg); + } try { this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName); } catch (NoSuchMethodException ex) { String msg = String.format( - "Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].", + "Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg, ex); @@ -1968,8 +2073,8 @@ public abstract class AnnotationUtils { private void validate() { // Target annotation is not meta-present? if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) { - String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " + - "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.", + String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " + + "an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg); @@ -1978,14 +2083,14 @@ public abstract class AnnotationUtils { if (this.isAliasPair) { AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class); if (mirrorAliasFor == null) { - String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].", + String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].", this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName); throw new AnnotationConfigurationException(msg); } String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute); if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) { - String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].", + String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].", this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName, mirrorAliasedAttributeName); throw new AnnotationConfigurationException(msg); @@ -1994,9 +2099,10 @@ public abstract class AnnotationUtils { Class<?> returnType = this.sourceAttribute.getReturnType(); Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType(); - if (returnType != aliasedReturnType) { - String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " + - "and attribute [%s] in annotation [%s] must declare the same return type.", + if (returnType != aliasedReturnType && + (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) { + String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + + "and attribute '%s' in annotation [%s] must declare the same return type.", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg); @@ -2013,16 +2119,16 @@ public abstract class AnnotationUtils { Object aliasedDefaultValue = aliasedAttribute.getDefaultValue(); if (defaultValue == null || aliasedDefaultValue == null) { - String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " + - "and attribute [%s] in annotation [%s] must declare default values.", + String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + + "and attribute '%s' in annotation [%s] must declare default values.", this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(), aliasedAttribute.getDeclaringClass().getName()); throw new AnnotationConfigurationException(msg); } if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) { - String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " + - "and attribute [%s] in annotation [%s] must declare the same default value.", + String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + + "and attribute '%s' in annotation [%s] must declare the same default value.", this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(), aliasedAttribute.getDeclaringClass().getName()); throw new AnnotationConfigurationException(msg); @@ -2125,15 +2231,16 @@ public abstract class AnnotationUtils { /** * Get the name of the aliased attribute configured via the supplied - * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}. + * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}, + * or the original attribute if no aliased one specified (indicating that + * the reference goes to a same-named attribute on a meta-annotation). * <p>This method returns the value of either the {@code attribute} * or {@code value} attribute of {@code @AliasFor}, ensuring that only * one of the attributes has been declared while simultaneously ensuring * that at least one of the attributes has been declared. * @param aliasFor the {@code @AliasFor} annotation from which to retrieve * the aliased attribute name - * @param attribute the attribute that is annotated with {@code @AliasFor}, - * used solely for building an exception message + * @param attribute the attribute that is annotated with {@code @AliasFor} * @return the name of the aliased attribute (never {@code null} or empty) * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected @@ -2146,23 +2253,15 @@ public abstract class AnnotationUtils { // Ensure user did not declare both 'value' and 'attribute' in @AliasFor if (attributeDeclared && valueDeclared) { - String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " + + String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " + "and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.", attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value); throw new AnnotationConfigurationException(msg); } + // Either explicit attribute name or pointing to same-named attribute by default attributeName = (attributeDeclared ? attributeName : value); - - // Ensure user declared either 'value' or 'attribute' in @AliasFor - if (!StringUtils.hasText(attributeName)) { - String msg = String.format( - "@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.", - attribute.getName(), attribute.getDeclaringClass().getName()); - throw new AnnotationConfigurationException(msg); - } - - return attributeName.trim(); + return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName()); } @Override |