Browse Source

Support searches for multiple merged annotations with "get" semantics

This commit picks up where a5139f3c66 left off with added support for
"get" search semantics for multiple merged annotations.

Specifically, this commit introduces a new getAllMergedAnnotations()
method in AnnotatedElementUtils.

Issue: SPR-13486
pull/1016/head
Sam Brannen 10 years ago
parent
commit
1ec35e9c62
  1. 91
      spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
  2. 180
      spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java

91
spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

@ -451,6 +451,38 @@ public class AnnotatedElementUtils { @@ -451,6 +451,38 @@ public class AnnotatedElementUtils {
return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
/**
* Get <strong>all</strong> annotations of the specified {@code annotationType}
* within the annotation hierarchy <em>above</em> the supplied {@code element};
* and for each annotation found, merge that annotation's attributes with
* <em>matching</em> attributes from annotations in lower levels of the annotation
* hierarchy and synthesize the results back into an annotation of the specified
* {@code annotationType}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
* single annotation and within annotation hierarchies.
* <p>This method follows <em>get semantics</em> 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 <A extends Annotation> Set<A> getAllMergedAnnotations(AnnotatedElement element,
Class<A> 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 <strong>all</strong> annotations of the specified
* {@code annotationName} in the annotation hierarchy above the supplied
@ -688,7 +720,7 @@ public class AnnotatedElementUtils { @@ -688,7 +720,7 @@ public class AnnotatedElementUtils {
* within the annotation hierarchy <em>above</em> the supplied {@code element};
* and for each annotation found, merge that annotation's attributes with
* <em>matching</em> 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}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
* single annotation and within annotation hierarchies.
@ -700,6 +732,7 @@ public class AnnotatedElementUtils { @@ -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 <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element,
Class<A> annotationType) {
@ -710,13 +743,7 @@ public class AnnotatedElementUtils { @@ -710,13 +743,7 @@ public class AnnotatedElementUtils {
MergedAnnotationAttributesProcessor processor =
new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true);
searchWithFindSemantics(element, annotationType, annotationType.getName(), processor);
Set<A> annotations = new LinkedHashSet<A>();
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 { @@ -724,7 +751,7 @@ public class AnnotatedElementUtils {
* within the annotation hierarchy <em>above</em> the supplied {@code element};
* and for each annotation found, merge that annotation's attributes with
* <em>matching</em> 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}.
* <p>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 { @@ -754,7 +781,7 @@ public class AnnotatedElementUtils {
* within the annotation hierarchy <em>above</em> the supplied {@code element};
* and for each annotation found, merge that annotation's attributes with
* <em>matching</em> 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}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
* single annotation and within annotation hierarchies.
@ -791,13 +818,7 @@ public class AnnotatedElementUtils { @@ -791,13 +818,7 @@ public class AnnotatedElementUtils {
MergedAnnotationAttributesProcessor processor =
new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true);
searchWithFindSemantics(element, annotationType, annotationType.getName(), containerType, processor);
Set<A> annotations = new LinkedHashSet<A>();
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 { @@ -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 { @@ -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 { @@ -1234,6 +1265,20 @@ public class AnnotatedElementUtils {
}
}
/**
* @since 4.3
*/
private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedResults(AnnotatedElement element,
Class<A> annotationType, List<AnnotationAttributes> aggregatedResults) {
Set<A> annotations = new LinkedHashSet<A>();
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 { @@ -1305,7 +1350,6 @@ public class AnnotatedElementUtils {
* Determine if this processor aggregates the results returned by {@link #process}.
* <p>If this method returns {@code true}, then {@link #getAggregatedResults()}
* must return a non-null value.
* <p>WARNING: aggregation is currently only supported for <em>find semantics</em>.
* @return {@code true} if this processor supports aggregated results
* @see #getAggregatedResults
* @since 4.3
@ -1319,7 +1363,6 @@ public class AnnotatedElementUtils { @@ -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.
* <p>WARNING: aggregation is currently only supported for <em>find semantics</em>.
* @return the list of results aggregated by this processor; never
* {@code null} unless {@link #aggregates} returns {@code false}
* @see #aggregates

180
spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java

@ -26,6 +26,7 @@ import java.lang.reflect.Method; @@ -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.*; @@ -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<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> 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<Cacheable> 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<MultipleComposedCachesOnInterfaceClass> element = MultipleComposedCachesOnInterfaceClass.class;
Set<Cacheable> 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<Cacheable> 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<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> 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<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> 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 { @@ -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<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
assertNotNull(cacheables);
assertEquals(2, cacheables.size());
Iterator<Cacheable> 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<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class);
@ -158,11 +277,40 @@ public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests { @@ -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 {

Loading…
Cancel
Save