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 726e5cf2173..382e3076274 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 @@ -40,9 +40,12 @@ import org.springframework.util.MultiValueMap; */ public class AnnotatedElementUtils { + private static final Boolean CONTINUE = null; + + /** - * Get the fully qualified class names of all meta-annotation types - * present on the annotation (of the specified + * Get the fully qualified class names of all meta-annotation + * types present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * *

This method finds all meta-annotations in the annotation hierarchy @@ -63,8 +66,8 @@ public class AnnotatedElementUtils { } /** - * Get the fully qualified class names of all meta-annotation types - * present on the annotation (of the specified + * Get the fully qualified class names of all meta-annotation + * types present on the annotation (of the specified * {@code annotationType}) on the supplied {@link AnnotatedElement}. * *

This method finds all meta-annotations in the annotation hierarchy @@ -92,7 +95,7 @@ public class AnnotatedElementUtils { @Override public Object process(Annotation annotation, int metaDepth) { types.add(annotation.annotationType().getName()); - return null; + return CONTINUE; } }, new HashSet(), 1); } @@ -126,10 +129,7 @@ public class AnnotatedElementUtils { @Override public Boolean process(Annotation annotation, int metaDepth) { boolean found = annotation.annotationType().getName().equals(annotationType); - if (found && (metaDepth > 0)) { - return Boolean.TRUE; - } - return null; + return ((found && (metaDepth > 0)) ? Boolean.TRUE : CONTINUE); } })); } @@ -155,7 +155,7 @@ public class AnnotatedElementUtils { @Override public Boolean process(Annotation annotation, int metaDepth) { boolean found = annotation.annotationType().getName().equals(annotationType); - return (found ? Boolean.TRUE : null); + return (found ? Boolean.TRUE : CONTINUE); } })); } @@ -197,7 +197,7 @@ public class AnnotatedElementUtils { */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(annotationType, + return processWithGetSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap)); } @@ -215,6 +215,7 @@ public class AnnotatedElementUtils { * @param annotationType the annotation type to find; never {@code null} * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found + * @since 4.2 */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, Class annotationType) { @@ -237,6 +238,7 @@ public class AnnotatedElementUtils { * type to find; never {@code null} or empty * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found + * @since 4.2 */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType) { return findAnnotationAttributes(element, annotationType, false, false); @@ -262,6 +264,7 @@ public class AnnotatedElementUtils { * as Annotation instances * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found + * @since 4.2 */ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { @@ -292,28 +295,47 @@ public class AnnotatedElementUtils { * as Annotation instances * @return the merged {@code AnnotationAttributes}, or {@code null} if * not found + * @since 4.2 */ private static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType, boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces, boolean searchOnMethodsInSuperclasses, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses, - searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor( + searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergedAnnotationAttributesProcessor( annotationType, classValuesAsString, nestedAnnotationsAsMap)); } /** + * Get the annotation attributes of all annotations + * of the specified {@code annotationType} in the annotation hierarchy above + * the supplied {@link AnnotatedElement} and store the results in a + * {@link MultiValueMap}. + * + *

Note: in contrast to {@link #getAnnotationAttributes(AnnotatedElement, String)}, + * this method does not take attribute overrides into account. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty - * @return a {@link MultiValueMap} containing the annotation attributes - * from all annotations found, or {@code null} if not found + * @return a {@link MultiValueMap} keyed by attribute name, containing + * the annotation attributes from all annotations found, or {@code null} + * if not found + * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) */ public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationType) { return getAllAnnotationAttributes(element, annotationType, false, false); } /** + * Get the annotation attributes of all annotations + * of the specified {@code annotationType} in the annotation hierarchy above + * the supplied {@link AnnotatedElement} and store the results in a + * {@link MultiValueMap}. + * + *

Note: in contrast to {@link #getAnnotationAttributes(AnnotatedElement, String)}, + * this method does not take attribute overrides into account. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation * type to find; never {@code null} or empty @@ -322,39 +344,34 @@ public class AnnotatedElementUtils { * @param nestedAnnotationsAsMap whether to convert nested Annotation * instances into {@link AnnotationAttributes} maps or to preserve them * as Annotation instances - * @return a {@link MultiValueMap} containing the annotation attributes - * from all annotations found, or {@code null} if not found + * @return a {@link MultiValueMap} keyed by attribute name, containing + * the annotation attributes from all annotations found, or {@code null} + * if not found */ public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { - final MultiValueMap attributes = new LinkedMultiValueMap(); + final MultiValueMap attributesMap = new LinkedMultiValueMap(); + + processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { - processWithGetSemantics(element, annotationType, new Processor() { @Override public Void process(Annotation annotation, int metaDepth) { - if (annotation.annotationType().getName().equals(annotationType)) { - for (Map.Entry entry : AnnotationUtils.getAnnotationAttributes( - annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { - attributes.add(entry.getKey(), entry.getValue()); + boolean found = annotation.annotationType().getName().equals(annotationType); + if (found) { + AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation, + classValuesAsString, nestedAnnotationsAsMap); + for (Map.Entry entry : annotationAttributes.entrySet()) { + attributesMap.add(entry.getKey(), entry.getValue()); } } + + // Continue searching... return null; } - @Override - public void postProcess(Annotation annotation, Void result) { - for (String key : attributes.keySet()) { - if (!AnnotationUtils.VALUE.equals(key)) { - Object value = AnnotationUtils.getValue(annotation, key); - if (value != null) { - attributes.add(key, value); - } - } - } - } }); - return (attributes.isEmpty() ? null : attributes); + return (attributesMap.isEmpty() ? null : attributesMap); } /** @@ -725,8 +742,8 @@ public class AnnotatedElementUtils { } /** - * {@link Processor} that only {@linkplain #process processes} annotations - * and does not {@link #postProcess} results. + * {@link Processor} that {@linkplain #process processes} annotations + * but does not {@link #postProcess} results. * @since 4.2 */ private abstract static class SimpleAnnotationProcessor implements Processor { @@ -748,14 +765,14 @@ public class AnnotatedElementUtils { * @see AnnotationUtils#getAnnotationAttributes(Annotation) * @since 4.2 */ - private static class MergeAnnotationAttributesProcessor implements Processor { + private static class MergedAnnotationAttributesProcessor implements Processor { private final String annotationType; private final boolean classValuesAsString; private final boolean nestedAnnotationsAsMap; - MergeAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + MergedAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { this.annotationType = annotationType; this.classValuesAsString = classValuesAsString; this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; @@ -764,16 +781,16 @@ public class AnnotatedElementUtils { @Override public AnnotationAttributes process(Annotation annotation, int metaDepth) { boolean found = annotation.annotationType().getName().equals(annotationType); - return (!found ? null : AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap)); + return (found ? AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap) : null); } @Override - public void postProcess(Annotation annotation, AnnotationAttributes result) { - for (String key : result.keySet()) { + public void postProcess(Annotation annotation, AnnotationAttributes attributes) { + for (String key : attributes.keySet()) { if (!AnnotationUtils.VALUE.equals(key)) { Object value = AnnotationUtils.getValue(annotation, key); if (value != null) { - result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); + attributes.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap)); } } } 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 7596a0ecaad..b46820fef5b 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 @@ -118,14 +118,26 @@ public class AnnotatedElementUtilsTests { assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName())); } + @Test + public void getAllAnnotationAttributesOnNonAnnotatedClass() { + assertNull(getAllAnnotationAttributes(NonAnnotatedClass.class, Transactional.class.getName())); + } + @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotation() { - MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, - Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, Transactional.class.getName()); assertNotNull("Annotation attributes map for @Transactional on TxConfig", attributes); assertEquals("value for TxConfig.", Arrays.asList("TxConfig"), attributes.get("value")); } + @Test + public void getAllAnnotationAttributesFavorsInheritedComposedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { + MultiValueMap attributes = getAllAnnotationAttributes(SubSubClassWithInheritedComposedAnnotation.class, + Transactional.class.getName()); + assertNotNull("Annotation attributes map for @Transactional on SubSubClassWithInheritedComposedAnnotation", attributes); + assertEquals(Arrays.asList("composed1"), attributes.get("qualifier")); + } + /** * If the "value" entry contains both "DerivedTxConfig" AND "TxConfig", then * the algorithm is accidentally picking up shadowed annotations of the same @@ -133,12 +145,11 @@ public class AnnotatedElementUtilsTests { * logic in {@link org.springframework.context.annotation.ProfileCondition} * to fail. * - * @see org.springframework.core.env.EnvironmentIntegrationTests#mostSpecificDerivedClassDrivesEnvironment_withDevEnvAndDerivedDevConfigClass + * @see org.springframework.core.env.EnvironmentSystemIntegrationTests#mostSpecificDerivedClassDrivesEnvironment_withDevEnvAndDerivedDevConfigClass */ @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotationThatShadowsAnnotationFromSuperclass() { - MultiValueMap attributes = getAllAnnotationAttributes(DerivedTxConfig.class, - Transactional.class.getName()); + MultiValueMap attributes = getAllAnnotationAttributes(DerivedTxConfig.class, Transactional.class.getName()); assertNotNull("Annotation attributes map for @Transactional on DerivedTxConfig", attributes); assertEquals("value for DerivedTxConfig.", Arrays.asList("DerivedTxConfig"), attributes.get("value")); } @@ -146,7 +157,7 @@ public class AnnotatedElementUtilsTests { /** * Note: this functionality is required by {@link org.springframework.context.annotation.ProfileCondition}. * - * @see org.springframework.core.env.EnvironmentIntegrationTests + * @see org.springframework.core.env.EnvironmentSystemIntegrationTests */ @Test public void getAllAnnotationAttributesOnClassWithMultipleComposedAnnotations() { @@ -211,7 +222,6 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should find @Transactional on ConcreteClassWithInheritedAnnotation", attributes); } - /** @since 4.2 */ @Test public void getAnnotationAttributesOnInheritedAnnotationInterface() { String name = Transactional.class.getName(); @@ -219,56 +229,48 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should get @Transactional on InheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(InheritedAnnotationInterface.class, Transactional.class); assertNotNull("Should find @Transactional on InheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnSubInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(SubInheritedAnnotationInterface.class, Transactional.class); assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnSubSubInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(SubSubInheritedAnnotationInterface.class, Transactional.class); assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnNonInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class); assertNotNull("Should find @Order on NonInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void getAnnotationAttributesOnNonInheritedAnnotationInterface() { AnnotationAttributes attributes = getAnnotationAttributes(NonInheritedAnnotationInterface.class, Order.class.getName()); assertNotNull("Should get @Order on NonInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnSubNonInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(SubNonInheritedAnnotationInterface.class, Order.class); assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnSubSubNonInheritedAnnotationInterface() { AnnotationAttributes attributes = findAnnotationAttributes(SubSubNonInheritedAnnotationInterface.class, Order.class); assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesInheritedFromInterfaceMethod() throws NoSuchMethodException { Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handleFromInterface"); @@ -276,7 +278,6 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should find @Order on ConcreteClassWithInheritedAnnotation.handleFromInterface() method", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesInheritedFromAbstractMethod() throws NoSuchMethodException { Method method = ConcreteClassWithInheritedAnnotation.class.getMethod("handle"); @@ -328,7 +329,6 @@ public class AnnotatedElementUtilsTests { assertNotNull("Should find @Order on StringGenericParameter.getFor() bridge method", attributes); } - /** @since 4.2 */ @Test public void findAnnotationAttributesOnClassWithMetaAndLocalTxConfig() { AnnotationAttributes attributes = findAnnotationAttributes(MetaAndLocalTxConfigClass.class, Transactional.class); @@ -379,7 +379,7 @@ public class AnnotatedElementUtilsTests { boolean readOnly() default false; } - @Transactional + @Transactional(qualifier = "composed1") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @@ -387,7 +387,7 @@ public class AnnotatedElementUtilsTests { @interface Composed1 { } - @Transactional(readOnly = true) + @Transactional(qualifier = "composed2", readOnly = true) @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented