Support @Cache* as merged composed annotations
Prior to this commit, @Cacheable, @CacheEvict, @CachePut, and @Caching could be used to create custom stereotype annotations with hardcoded values for their attributes; however, it was not possible to create composed annotations with attribute overrides. This commit addresses this issue by refactoring SpringCacheAnnotationParser to use the newly introduced findAllMergedAnnotations() method in AnnotatedElementUtils. As a result, @Cacheable, @CacheEvict, @CachePut, and @Caching can now be used to create custom composed annotations with attribute overrides configured via @AliasFor. Issue: SPR-13475
This commit is contained in:
@@ -27,6 +27,7 @@ import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||
import org.springframework.cache.interceptor.CacheOperation;
|
||||
import org.springframework.cache.interceptor.CachePutOperation;
|
||||
import org.springframework.cache.interceptor.CacheableOperation;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -61,29 +62,29 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
|
||||
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
|
||||
Collection<CacheOperation> ops = null;
|
||||
|
||||
Collection<Cacheable> cacheables = getAnnotations(ae, Cacheable.class);
|
||||
if (cacheables != null) {
|
||||
Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
|
||||
if (!cacheables.isEmpty()) {
|
||||
ops = lazyInit(ops);
|
||||
for (Cacheable cacheable : cacheables) {
|
||||
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
|
||||
}
|
||||
}
|
||||
Collection<CacheEvict> evicts = getAnnotations(ae, CacheEvict.class);
|
||||
if (evicts != null) {
|
||||
Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
|
||||
if (!evicts.isEmpty()) {
|
||||
ops = lazyInit(ops);
|
||||
for (CacheEvict evict : evicts) {
|
||||
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
|
||||
}
|
||||
}
|
||||
Collection<CachePut> puts = getAnnotations(ae, CachePut.class);
|
||||
if (puts != null) {
|
||||
Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
|
||||
if (!puts.isEmpty()) {
|
||||
ops = lazyInit(ops);
|
||||
for (CachePut put : puts) {
|
||||
ops.add(parsePutAnnotation(ae, cachingConfig, put));
|
||||
}
|
||||
}
|
||||
Collection<Caching> cachings = getAnnotations(ae, Caching.class);
|
||||
if (cachings != null) {
|
||||
Collection<Caching> cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
|
||||
if (!cachings.isEmpty()) {
|
||||
ops = lazyInit(ops);
|
||||
for (Caching caching : cachings) {
|
||||
ops.addAll(parseCachingAnnotation(ae, cachingConfig, caching));
|
||||
@@ -198,26 +199,6 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
|
||||
return new DefaultCacheConfig();
|
||||
}
|
||||
|
||||
private <A extends Annotation> Collection<A> getAnnotations(AnnotatedElement ae, Class<A> annotationType) {
|
||||
Collection<A> anns = new ArrayList<A>(1);
|
||||
|
||||
// look at raw annotation
|
||||
A ann = ae.getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
|
||||
}
|
||||
|
||||
// scan meta-annotations
|
||||
for (Annotation metaAnn : ae.getAnnotations()) {
|
||||
ann = metaAnn.annotationType().getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
|
||||
}
|
||||
}
|
||||
|
||||
return (!anns.isEmpty() ? anns : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the specified {@link CacheOperation}.
|
||||
* <p>Throws an {@link IllegalStateException} if the state of the operation is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,7 +26,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
@@ -105,34 +104,46 @@ public class AnnotationCacheOperationSourceTests {
|
||||
assertTrue(next.getCacheNames().contains("bar"));
|
||||
}
|
||||
|
||||
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
|
||||
@Ignore("Disabled until SPR-13475 is resolved")
|
||||
@Test
|
||||
public void singleComposedAnnotation() throws Exception {
|
||||
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 1);
|
||||
CacheOperation cacheOperation = ops.iterator().next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composed")));
|
||||
}
|
||||
|
||||
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
|
||||
@Ignore("Disabled until SPR-13475 is resolved")
|
||||
@Test
|
||||
public void multipleComposedAnnotations() throws Exception {
|
||||
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 3);
|
||||
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 2);
|
||||
Iterator<CacheOperation> it = ops.iterator();
|
||||
|
||||
CacheOperation cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
|
||||
assertThat(cacheOperation.getKey(), equalTo(""));
|
||||
|
||||
cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
|
||||
assertThat(cacheOperation.getKey(), equalTo("composedKey"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleComposedAnnotations() throws Exception {
|
||||
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 4);
|
||||
Iterator<CacheOperation> it = ops.iterator();
|
||||
|
||||
CacheOperation cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
|
||||
assertThat(cacheOperation.getKey(), equalTo(""));
|
||||
|
||||
cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
|
||||
assertThat(cacheOperation.getKey(), equalTo("composedKey"));
|
||||
|
||||
cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("foo")));
|
||||
assertThat(cacheOperation.getKey(), equalTo(""));
|
||||
|
||||
cacheOperation = it.next();
|
||||
assertThat(cacheOperation, instanceOf(CacheEvictOperation.class));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
|
||||
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCacheEvict")));
|
||||
assertThat(cacheOperation.getKey(), equalTo("composedEvictionKey"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -309,13 +320,15 @@ public class AnnotationCacheOperationSourceTests {
|
||||
public void multipleStereotype() {
|
||||
}
|
||||
|
||||
@ComposedCacheable("composed")
|
||||
@Cacheable("directly declared")
|
||||
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
|
||||
public void singleComposed() {
|
||||
}
|
||||
|
||||
@Cacheable("directly declared")
|
||||
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
|
||||
@CacheableFoo
|
||||
@ComposedCacheEvict(cacheNames = "composedCache", key = "composedKey")
|
||||
@ComposedCacheEvict(cacheNames = "composedCacheEvict", key = "composedEvictionKey")
|
||||
public void multipleComposed() {
|
||||
}
|
||||
|
||||
@@ -443,38 +456,37 @@ public class AnnotationCacheOperationSourceTests {
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@CacheConfig(keyGenerator = "classKeyGenerator",
|
||||
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
|
||||
@CacheConfig(keyGenerator = "classKeyGenerator", cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
|
||||
public @interface CacheConfigFoo {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Cacheable(cacheNames = "shadowed cache name", key = "shadowed key")
|
||||
public @interface ComposedCacheable {
|
||||
@interface ComposedCacheable {
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
|
||||
@AliasFor(annotation = Cacheable.class)
|
||||
String[] value() default {};
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
|
||||
@AliasFor(annotation = Cacheable.class)
|
||||
String[] cacheNames() default {};
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "key")
|
||||
@AliasFor(annotation = Cacheable.class)
|
||||
String key() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@CacheEvict(cacheNames = "shadowed cache name", key = "shadowed key")
|
||||
public @interface ComposedCacheEvict {
|
||||
@interface ComposedCacheEvict {
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
|
||||
@AliasFor(annotation = CacheEvict.class)
|
||||
String[] value() default {};
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
|
||||
@AliasFor(annotation = CacheEvict.class)
|
||||
String[] cacheNames() default {};
|
||||
|
||||
@AliasFor(annotation = Cacheable.class, attribute = "key")
|
||||
@AliasFor(annotation = CacheEvict.class)
|
||||
String key() default "";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user