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 c52937b7a1e..2447039dbee 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 @@ -451,6 +451,38 @@ public class AnnotatedElementUtils { return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element); } + /** + * Get all annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

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

This method follows get semantics as described in the + * {@linkplain AnnotatedElementUtils class-level javadoc}. + * @param element the annotated element; never {@code null} + * @param annotationType the annotation type to find; never {@code null} + * @return the set of all merged, synthesized {@code Annotations} found, or an empty + * set if none were found + * @since 4.3 + * @see #getMergedAnnotation(AnnotatedElement, Class) + * @see #getAllAnnotationAttributes(AnnotatedElement, String) + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set getAllMergedAnnotations(AnnotatedElement element, + Class annotationType) { + + Assert.notNull(element, "AnnotatedElement must not be null"); + Assert.notNull(annotationType, "annotationType must not be null"); + + MergedAnnotationAttributesProcessor processor = + new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true); + searchWithGetSemantics(element, annotationType, null, processor); + return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults()); + } + /** * Get the annotation attributes of all annotations of the specified * {@code annotationName} in the annotation hierarchy above the supplied @@ -688,7 +720,7 @@ public class AnnotatedElementUtils { * within the annotation hierarchy above the supplied {@code element}; * and for each annotation found, 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 + * hierarchy and synthesize the results back into an annotation of the specified * {@code annotationType}. *

{@link AliasFor @AliasFor} semantics are fully supported, both within a * single annotation and within annotation hierarchies. @@ -700,6 +732,7 @@ public class AnnotatedElementUtils { * set if none were found * @since 4.3 * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ public static Set findAllMergedAnnotations(AnnotatedElement element, Class annotationType) { @@ -710,13 +743,7 @@ public class AnnotatedElementUtils { MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true); searchWithFindSemantics(element, annotationType, annotationType.getName(), processor); - - Set annotations = new LinkedHashSet(); - for (AnnotationAttributes attributes : processor.getAggregatedResults()) { - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); - annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element)); - } - return annotations; + return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults()); } /** @@ -724,7 +751,7 @@ public class AnnotatedElementUtils { * within the annotation hierarchy above the supplied {@code element}; * and for each annotation found, 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 + * hierarchy and synthesize the results back into an annotation of the specified * {@code annotationType}. *

The container type that holds the repeatable annotations will be looked up * via {@link java.lang.annotation.Repeatable}. @@ -754,7 +781,7 @@ public class AnnotatedElementUtils { * within the annotation hierarchy above the supplied {@code element}; * and for each annotation found, 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 + * hierarchy and synthesize the results back into an annotation of the specified * {@code annotationType}. *

{@link AliasFor @AliasFor} semantics are fully supported, both within a * single annotation and within annotation hierarchies. @@ -791,13 +818,7 @@ public class AnnotatedElementUtils { MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true); searchWithFindSemantics(element, annotationType, annotationType.getName(), containerType, processor); - - Set annotations = new LinkedHashSet(); - for (AnnotationAttributes attributes : processor.getAggregatedResults()) { - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); - annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element)); - } - return annotations; + return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults()); } /** @@ -906,13 +927,18 @@ public class AnnotatedElementUtils { // Search in annotations for (Annotation annotation : annotations) { + // Note: we only check for (metaDepth > 0) due to the nuances of getMetaAnnotationTypes(). if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) && - ((annotationType != null ? annotation.annotationType() == annotationType : - annotation.annotationType().getName().equals(annotationName)) || - metaDepth > 0)) { + ((annotation.annotationType() == annotationType + || annotation.annotationType().getName().equals(annotationName)) || metaDepth > 0)) { T result = processor.process(annotatedElement, annotation, metaDepth); if (result != null) { - return result; + if (processor.aggregates() && metaDepth == 0) { + processor.getAggregatedResults().add(result); + } + else { + return result; + } } } } @@ -924,7 +950,12 @@ public class AnnotatedElementUtils { annotationName, processor, visited, metaDepth + 1); if (result != null) { processor.postProcess(annotatedElement, annotation, result); - return result; + if (processor.aggregates() && metaDepth == 0) { + processor.getAggregatedResults().add(result); + } + else { + return result; + } } } } @@ -1234,6 +1265,20 @@ public class AnnotatedElementUtils { } } + /** + * @since 4.3 + */ + private static Set postProcessAndSynthesizeAggregatedResults(AnnotatedElement element, + Class annotationType, List aggregatedResults) { + + Set annotations = new LinkedHashSet(); + for (AnnotationAttributes attributes : aggregatedResults) { + AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); + annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element)); + } + return annotations; + } + /** * Callback interface that is used to process annotations during a search. @@ -1305,7 +1350,6 @@ public class AnnotatedElementUtils { * Determine if this processor aggregates the results returned by {@link #process}. *

If this method returns {@code true}, then {@link #getAggregatedResults()} * must return a non-null value. - *

WARNING: aggregation is currently only supported for find semantics. * @return {@code true} if this processor supports aggregated results * @see #getAggregatedResults * @since 4.3 @@ -1319,7 +1363,6 @@ public class AnnotatedElementUtils { * responsible for asking this processor if it {@link #aggregates} results * and then adding the post-processed results to the list returned by this * method. - *

WARNING: aggregation is currently only supported for find semantics. * @return the list of results aggregated by this processor; never * {@code null} unless {@link #aggregates} returns {@code false} * @see #aggregates diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java index 3083793a21a..395b9c31b53 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java @@ -26,6 +26,7 @@ import java.lang.reflect.Method; import java.util.Iterator; import java.util.Set; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -45,43 +46,145 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*; public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests { @Test - public void multipleComposedAnnotationsOnClass() { - assertMultipleComposedAnnotations(MultipleComposedCachesClass.class); + public void getMultipleComposedAnnotationsOnClass() { + assertGetAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class); } @Test - public void composedPlusLocalAnnotationsOnClass() { - assertMultipleComposedAnnotations(ComposedPlusLocalCachesClass.class); + public void getMultipleInheritedComposedAnnotationsOnSuperclass() { + assertGetAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class); } @Test - public void multipleComposedAnnotationsOnInterface() { - assertMultipleComposedAnnotations(MultipleComposedCachesOnInterfaceClass.class); + public void getMultipleNoninheritedComposedAnnotationsOnClass() { + Class element = MultipleNoninheritedComposedCachesClass.class; + Set cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void getMultipleNoninheritedComposedAnnotationsOnSuperclass() { + Class element = SubMultipleNoninheritedComposedCachesClass.class; + Set cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); + } + + @Test + public void getComposedPlusLocalAnnotationsOnClass() { + assertGetAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class); } @Test - public void composedCacheOnInterfaceAndLocalCacheOnClass() { - assertMultipleComposedAnnotations(ComposedCacheOnInterfaceAndLocalCacheClass.class); + public void getMultipleComposedAnnotationsOnInterface() { + Class element = MultipleComposedCachesOnInterfaceClass.class; + Set cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); } @Test - public void multipleComposedAnnotationsOnMethod() throws Exception { + public void getMultipleComposedAnnotationsOnMethod() throws Exception { AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod"); - assertMultipleComposedAnnotations(element); + assertGetAllMergedAnnotationsBehavior(element); } @Test - public void composedPlusLocalAnnotationsOnMethod() throws Exception { + public void getComposedPlusLocalAnnotationsOnMethod() throws Exception { AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod"); - assertMultipleComposedAnnotations(element); + assertGetAllMergedAnnotationsBehavior(element); + } + + @Test + @Ignore("Disabled since some Java 8 updates handle the bridge method differently") + public void getMultipleComposedAnnotationsOnBridgeMethod() throws Exception { + Set cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); + } + + @Test + public void findMultipleComposedAnnotationsOnClass() { + assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class); + } + + @Test + public void findMultipleInheritedComposedAnnotationsOnSuperclass() { + assertFindAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class); + } + + @Test + public void findMultipleNoninheritedComposedAnnotationsOnClass() { + Class element = MultipleNoninheritedComposedCachesClass.class; + Set cacheables = findAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void findMultipleNoninheritedComposedAnnotationsOnSuperclass() { + Class element = SubMultipleNoninheritedComposedCachesClass.class; + Set cacheables = findAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void findComposedPlusLocalAnnotationsOnClass() { + assertFindAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class); + } + + @Test + public void findMultipleComposedAnnotationsOnInterface() { + assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesOnInterfaceClass.class); + } + + @Test + public void findComposedCacheOnInterfaceAndLocalCacheOnClass() { + assertFindAllMergedAnnotationsBehavior(ComposedCacheOnInterfaceAndLocalCacheClass.class); + } + + @Test + public void findMultipleComposedAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod"); + assertFindAllMergedAnnotationsBehavior(element); + } + + @Test + public void findComposedPlusLocalAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod"); + assertFindAllMergedAnnotationsBehavior(element); + } + + @Test + public void findMultipleComposedAnnotationsOnBridgeMethod() throws Exception { + assertFindAllMergedAnnotationsBehavior(getBridgeMethod()); } /** * Bridge/bridged method setup code copied from * {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}. */ - @Test - public void multipleComposedAnnotationsBridgeMethod() throws NoSuchMethodException { + public Method getBridgeMethod() throws NoSuchMethodException { Method[] methods = StringGenericParameter.class.getMethods(); Method bridgeMethod = null; Method bridgedMethod = null; @@ -99,10 +202,26 @@ public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests { assertTrue(bridgeMethod != null && bridgeMethod.isBridge()); assertTrue(bridgedMethod != null && !bridgedMethod.isBridge()); - assertMultipleComposedAnnotations(bridgeMethod); + return bridgeMethod; + } + + private void assertGetAllMergedAnnotationsBehavior(AnnotatedElement element) { + assertNotNull(element); + + Set cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator iterator = cacheables.iterator(); + Cacheable fooCacheable = iterator.next(); + Cacheable barCacheable = iterator.next(); + assertEquals("fooKey", fooCacheable.key()); + assertEquals("fooCache", fooCacheable.value()); + assertEquals("barKey", barCacheable.key()); + assertEquals("barCache", barCacheable.value()); } - private void assertMultipleComposedAnnotations(AnnotatedElement element) { + private void assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) { assertNotNull(element); Set cacheables = findAllMergedAnnotations(element, Cacheable.class); @@ -158,11 +277,40 @@ public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests { String key(); } + @Cacheable("noninheritedCache1") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @interface NoninheritedCache1 { + + @AliasFor(annotation = Cacheable.class) + String key() default ""; + } + + @Cacheable("noninheritedCache2") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @interface NoninheritedCache2 { + + @AliasFor(annotation = Cacheable.class) + String key() default ""; + } + @FooCache(key = "fooKey") @BarCache(key = "barKey") private static class MultipleComposedCachesClass { } + private static class SubMultipleComposedCachesClass extends MultipleComposedCachesClass { + } + + @NoninheritedCache1 + @NoninheritedCache2 + private static class MultipleNoninheritedComposedCachesClass { + } + + private static class SubMultipleNoninheritedComposedCachesClass extends MultipleNoninheritedComposedCachesClass { + } + @Cacheable(cacheName = "fooCache", key = "fooKey") @BarCache(key = "barKey") private static class ComposedPlusLocalCachesClass {