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
This commit is contained in:
parent
3eff478c45
commit
1ec35e9c62
|
|
@ -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 {
|
|||
* 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 {
|
|||
* 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 {
|
|||
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 {
|
|||
* 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 {
|
|||
* 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 {
|
|||
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 {
|
|||
|
||||
// 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 <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 {
|
|||
* 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 {
|
|||
* 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
|
||||
|
|
|
|||
|
|
@ -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<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 composedCacheOnInterfaceAndLocalCacheOnClass() {
|
||||
assertMultipleComposedAnnotations(ComposedCacheOnInterfaceAndLocalCacheClass.class);
|
||||
public void getMultipleNoninheritedComposedAnnotationsOnSuperclass() {
|
||||
Class<?> element = SubMultipleNoninheritedComposedCachesClass.class;
|
||||
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
|
||||
assertNotNull(cacheables);
|
||||
assertEquals(0, cacheables.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleComposedAnnotationsOnMethod() throws Exception {
|
||||
public void getComposedPlusLocalAnnotationsOnClass() {
|
||||
assertGetAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMultipleComposedAnnotationsOnInterface() {
|
||||
Class<MultipleComposedCachesOnInterfaceClass> element = MultipleComposedCachesOnInterfaceClass.class;
|
||||
Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class);
|
||||
assertNotNull(cacheables);
|
||||
assertEquals(0, cacheables.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
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 {
|
|||
assertTrue(bridgeMethod != null && bridgeMethod.isBridge());
|
||||
assertTrue(bridgedMethod != null && !bridgedMethod.isBridge());
|
||||
|
||||
assertMultipleComposedAnnotations(bridgeMethod);
|
||||
return bridgeMethod;
|
||||
}
|
||||
|
||||
private void assertMultipleComposedAnnotations(AnnotatedElement element) {
|
||||
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 assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) {
|
||||
assertNotNull(element);
|
||||
|
||||
Set<Cacheable> 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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue