Skip to content

Commit 59c88eb

Browse files
committed
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
1 parent 2a715e9 commit 59c88eb

File tree

2 files changed

+44
-51
lines changed

2 files changed

+44
-51
lines changed

spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.cache.interceptor.CacheOperation;
2828
import org.springframework.cache.interceptor.CachePutOperation;
2929
import org.springframework.cache.interceptor.CacheableOperation;
30+
import org.springframework.core.annotation.AnnotatedElementUtils;
3031
import org.springframework.core.annotation.AnnotationUtils;
3132
import org.springframework.util.ObjectUtils;
3233
import org.springframework.util.StringUtils;
@@ -61,29 +62,29 @@ public Collection<CacheOperation> parseCacheAnnotations(Method method) {
6162
protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
6263
Collection<CacheOperation> ops = null;
6364

64-
Collection<Cacheable> cacheables = getAnnotations(ae, Cacheable.class);
65-
if (cacheables != null) {
65+
Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
66+
if (!cacheables.isEmpty()) {
6667
ops = lazyInit(ops);
6768
for (Cacheable cacheable : cacheables) {
6869
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
6970
}
7071
}
71-
Collection<CacheEvict> evicts = getAnnotations(ae, CacheEvict.class);
72-
if (evicts != null) {
72+
Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
73+
if (!evicts.isEmpty()) {
7374
ops = lazyInit(ops);
7475
for (CacheEvict evict : evicts) {
7576
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
7677
}
7778
}
78-
Collection<CachePut> puts = getAnnotations(ae, CachePut.class);
79-
if (puts != null) {
79+
Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
80+
if (!puts.isEmpty()) {
8081
ops = lazyInit(ops);
8182
for (CachePut put : puts) {
8283
ops.add(parsePutAnnotation(ae, cachingConfig, put));
8384
}
8485
}
85-
Collection<Caching> cachings = getAnnotations(ae, Caching.class);
86-
if (cachings != null) {
86+
Collection<Caching> cachings = AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class);
87+
if (!cachings.isEmpty()) {
8788
ops = lazyInit(ops);
8889
for (Caching caching : cachings) {
8990
ops.addAll(parseCachingAnnotation(ae, cachingConfig, caching));
@@ -198,26 +199,6 @@ DefaultCacheConfig getDefaultCacheConfig(Class<?> target) {
198199
return new DefaultCacheConfig();
199200
}
200201

201-
private <A extends Annotation> Collection<A> getAnnotations(AnnotatedElement ae, Class<A> annotationType) {
202-
Collection<A> anns = new ArrayList<A>(1);
203-
204-
// look at raw annotation
205-
A ann = ae.getAnnotation(annotationType);
206-
if (ann != null) {
207-
anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
208-
}
209-
210-
// scan meta-annotations
211-
for (Annotation metaAnn : ae.getAnnotations()) {
212-
ann = metaAnn.annotationType().getAnnotation(annotationType);
213-
if (ann != null) {
214-
anns.add(AnnotationUtils.synthesizeAnnotation(ann, ae));
215-
}
216-
}
217-
218-
return (!anns.isEmpty() ? anns : null);
219-
}
220-
221202
/**
222203
* Validates the specified {@link CacheOperation}.
223204
* <p>Throws an {@link IllegalStateException} if the state of the operation is

spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@
2626
import java.util.Collections;
2727
import java.util.Iterator;
2828

29-
import org.junit.Ignore;
3029
import org.junit.Rule;
3130
import org.junit.Test;
3231
import org.junit.rules.ExpectedException;
@@ -105,34 +104,46 @@ public void multipleStereotypes() throws Exception {
105104
assertTrue(next.getCacheNames().contains("bar"));
106105
}
107106

108-
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
109-
@Ignore("Disabled until SPR-13475 is resolved")
110107
@Test
111108
public void singleComposedAnnotation() throws Exception {
112-
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 1);
113-
CacheOperation cacheOperation = ops.iterator().next();
109+
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 2);
110+
Iterator<CacheOperation> it = ops.iterator();
111+
112+
CacheOperation cacheOperation = it.next();
113+
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
114+
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
115+
assertThat(cacheOperation.getKey(), equalTo(""));
116+
117+
cacheOperation = it.next();
114118
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
115-
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composed")));
119+
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
120+
assertThat(cacheOperation.getKey(), equalTo("composedKey"));
116121
}
117122

118-
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
119-
@Ignore("Disabled until SPR-13475 is resolved")
120123
@Test
121124
public void multipleComposedAnnotations() throws Exception {
122-
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 3);
125+
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 4);
123126
Iterator<CacheOperation> it = ops.iterator();
124127

125128
CacheOperation cacheOperation = it.next();
126129
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
130+
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("directly declared")));
131+
assertThat(cacheOperation.getKey(), equalTo(""));
132+
133+
cacheOperation = it.next();
134+
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
127135
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
136+
assertThat(cacheOperation.getKey(), equalTo("composedKey"));
128137

129138
cacheOperation = it.next();
130139
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
131140
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("foo")));
141+
assertThat(cacheOperation.getKey(), equalTo(""));
132142

133143
cacheOperation = it.next();
134144
assertThat(cacheOperation, instanceOf(CacheEvictOperation.class));
135-
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
145+
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCacheEvict")));
146+
assertThat(cacheOperation.getKey(), equalTo("composedEvictionKey"));
136147
}
137148

138149
@Test
@@ -309,13 +320,15 @@ public void singleStereotype() {
309320
public void multipleStereotype() {
310321
}
311322

312-
@ComposedCacheable("composed")
323+
@Cacheable("directly declared")
324+
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
313325
public void singleComposed() {
314326
}
315327

328+
@Cacheable("directly declared")
316329
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
317330
@CacheableFoo
318-
@ComposedCacheEvict(cacheNames = "composedCache", key = "composedKey")
331+
@ComposedCacheEvict(cacheNames = "composedCacheEvict", key = "composedEvictionKey")
319332
public void multipleComposed() {
320333
}
321334

@@ -443,38 +456,37 @@ public void multipleCacheConfig() {
443456

444457
@Retention(RetentionPolicy.RUNTIME)
445458
@Target(ElementType.TYPE)
446-
@CacheConfig(keyGenerator = "classKeyGenerator",
447-
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
459+
@CacheConfig(keyGenerator = "classKeyGenerator", cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
448460
public @interface CacheConfigFoo {
449461
}
450462

451463
@Retention(RetentionPolicy.RUNTIME)
452464
@Target({ ElementType.METHOD, ElementType.TYPE })
453465
@Cacheable(cacheNames = "shadowed cache name", key = "shadowed key")
454-
public @interface ComposedCacheable {
466+
@interface ComposedCacheable {
455467

456-
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
468+
@AliasFor(annotation = Cacheable.class)
457469
String[] value() default {};
458470

459-
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
471+
@AliasFor(annotation = Cacheable.class)
460472
String[] cacheNames() default {};
461473

462-
@AliasFor(annotation = Cacheable.class, attribute = "key")
474+
@AliasFor(annotation = Cacheable.class)
463475
String key() default "";
464476
}
465477

466478
@Retention(RetentionPolicy.RUNTIME)
467479
@Target({ ElementType.METHOD, ElementType.TYPE })
468480
@CacheEvict(cacheNames = "shadowed cache name", key = "shadowed key")
469-
public @interface ComposedCacheEvict {
481+
@interface ComposedCacheEvict {
470482

471-
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
483+
@AliasFor(annotation = CacheEvict.class)
472484
String[] value() default {};
473485

474-
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames")
486+
@AliasFor(annotation = CacheEvict.class)
475487
String[] cacheNames() default {};
476488

477-
@AliasFor(annotation = Cacheable.class, attribute = "key")
489+
@AliasFor(annotation = CacheEvict.class)
478490
String key() default "";
479491
}
480492

0 commit comments

Comments
 (0)