diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 5f2c3341ad..d7b3617a22 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -218,6 +218,32 @@ public class AnnotatedElementUtils { })); } + /** + * Get the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied {@code element}, + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy, and synthesize + * the result back into an annotation of the specified {@code annotationType}. + * + *

{@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + * + *

This method delegates to {@link #getAnnotationAttributes(AnnotatedElement, Class)} + * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}. + * + * @param element the annotated element; never {@code null} + * @param annotationType the annotation type to find; never {@code null} + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #getAnnotationAttributes(AnnotatedElement, Class) + * @see #findAnnotation(AnnotatedElement, Class) + * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) + */ + public static A getAnnotation(AnnotatedElement element, Class annotationType) { + AnnotationAttributes attributes = getAnnotationAttributes(element, annotationType); + return ((attributes != null) ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); + } + /** * Get the first annotation of the specified {@code annotationType} within * the annotation hierarchy above the supplied {@code element} and @@ -232,9 +258,10 @@ public class AnnotatedElementUtils { * @param element the annotated element; never {@code null} * @param annotationType the annotation type to find; never {@code null} * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 * @see #getAnnotationAttributes(AnnotatedElement, String, boolean, boolean) - * @see #getAllAnnotationAttributes(AnnotatedElement, String) * @see #findAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #getAnnotation(AnnotatedElement, Class) * @see #findAnnotation(AnnotatedElement, Class) */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, @@ -945,8 +972,9 @@ public class AnnotatedElementUtils { * target annotation during the {@link #process} phase and then merges * annotation attributes from lower levels in the annotation hierarchy * during the {@link #postProcess} phase. - * @see AnnotationUtils#getAnnotationAttributes(Annotation) * @since 4.2 + * @see AnnotationUtils#getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean, boolean) + * @see AnnotationUtils#postProcessAnnotationAttributes */ private static class MergedAnnotationAttributesProcessor implements Processor { 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 8e8affbd8b..199988e9c6 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 @@ -766,8 +766,8 @@ public abstract class AnnotationUtils { * @param annotation the annotation to retrieve the attributes for * @return the annotation attributes (a specialized Map) with attribute names as keys * and corresponding attribute values as values; never {@code null} - * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) * @since 4.2 + * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation) { return getAnnotationAttributes(annotatedElement, annotation, false, false); @@ -1233,8 +1233,8 @@ public abstract class AnnotationUtils { * not from an annotation, or if the supplied target type is {@link Annotation} * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected - * @see #getAliasedAttributeName(Method, Class) * @since 4.2 + * @see #getAliasedAttributeName(Method, Class) */ static String getAliasedAttributeName(Method attribute) { return getAliasedAttributeName(attribute, null); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 3b80dc6f6f..96e8546a41 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -322,6 +322,19 @@ public class AnnotatedElementUtilsTests { assertTrue(isAnnotated(element, name)); } + @Test + public void getAnnotationWithAliasedValueComposedAnnotation() { + Class element = AliasedValueComposedContextConfigClass.class; + ContextConfig contextConfig = getAnnotation(element, ContextConfig.class); + + assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), contextConfig); + assertArrayEquals("locations", new String[] { "test.xml" }, contextConfig.locations()); + assertArrayEquals("value", new String[] { "test.xml" }, contextConfig.value()); + + // Verify contracts between utility methods: + assertTrue(isAnnotated(element, ContextConfig.class.getName())); + } + @Test public void getAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() { Class element = InvalidConventionBasedComposedContextConfigClass.class;