Skip to content

Commit 08c032d

Browse files
committed
Allow Cache annotations to not specify any cache name
Since Spring 4.1, a CacheResolver may be configured to customize the way the cache(s) to use for a given cache operation are retrieved. Since a CacheResolver implementation may not use the cache names information at all, this attribute has been made optional. However, a fix was still applied, preventing a Cache operation without a cache name to be defined properly. We now allow this valid use case. Issue: SPR-13081
1 parent d605618 commit 08c032d

File tree

3 files changed

+86
-13
lines changed

3 files changed

+86
-13
lines changed

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,6 @@ private void validateCacheOperation(AnnotatedElement ae, CacheOperation operatio
236236
"default cache resolver if none is set. If a cache resolver is set, the cache manager" +
237237
"won't be used.");
238238
}
239-
if (operation.getCacheNames().isEmpty()) {
240-
throw new IllegalStateException("No cache names could be detected on '" +
241-
ae.toString() + "'. Make sure to set the value parameter on the annotation or " +
242-
"declare a @CacheConfig at the class-level with the default cache name(s) to use.");
243-
}
244239
}
245240

246241
@Override

spring-context/src/test/java/org/springframework/cache/CacheReproTests.java

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,24 @@
1717
package org.springframework.cache;
1818

1919
import java.util.Arrays;
20+
import java.util.Collection;
2021
import java.util.Collections;
2122
import java.util.List;
2223

24+
import org.junit.Rule;
2325
import org.junit.Test;
24-
26+
import org.junit.rules.ExpectedException;
2527
import org.mockito.Mockito;
2628

2729
import org.springframework.cache.annotation.Cacheable;
2830
import org.springframework.cache.annotation.Caching;
31+
import org.springframework.cache.annotation.CachingConfigurerSupport;
2932
import org.springframework.cache.annotation.EnableCaching;
3033
import org.springframework.cache.concurrent.ConcurrentMapCache;
3134
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
35+
import org.springframework.cache.interceptor.AbstractCacheResolver;
36+
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
37+
import org.springframework.cache.interceptor.CacheResolver;
3238
import org.springframework.cache.support.SimpleCacheManager;
3339
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3440
import org.springframework.context.annotation.Bean;
@@ -46,6 +52,9 @@
4652
*/
4753
public class CacheReproTests {
4854

55+
@Rule
56+
public final ExpectedException thrown = ExpectedException.none();
57+
4958
@Test
5059
public void spr11124() throws Exception {
5160
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11124Config.class);
@@ -84,7 +93,7 @@ public void spr11592GetSimple() {
8493
}
8594

8695
@Test
87-
public void spr11592GetNeverCache(){
96+
public void spr11592GetNeverCache() {
8897
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11592Config.class);
8998
Spr11592Service bean = context.getBean(Spr11592Service.class);
9099
Cache cache = context.getBean("cache", Cache.class);
@@ -100,6 +109,27 @@ public void spr11592GetNeverCache(){
100109
context.close();
101110
}
102111

112+
@Test
113+
public void spr13081ConfigNoCacheNameIsRequired() {
114+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr13081Config.class);
115+
MyCacheResolver cacheResolver = context.getBean(MyCacheResolver.class);
116+
Spr13081Service bean = context.getBean(Spr13081Service.class);
117+
assertNull(cacheResolver.getCache("foo").get("foo"));
118+
Object result = bean.getSimple("foo"); // cache name = id
119+
assertEquals(result, cacheResolver.getCache("foo").get("foo").get());
120+
}
121+
122+
@Test
123+
public void spr13081ConfigFailIfCacheResolverReturnsNullCacheName() {
124+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr13081Config.class);
125+
Spr13081Service bean = context.getBean(Spr13081Service.class);
126+
127+
128+
thrown.expect(IllegalStateException.class);
129+
thrown.expectMessage(MyCacheResolver.class.getName());
130+
bean.getSimple(null);
131+
}
132+
103133

104134
@Configuration
105135
@EnableCaching
@@ -119,9 +149,9 @@ public Spr11124Service service() {
119149

120150
public interface Spr11124Service {
121151

122-
public List<String> single(int id);
152+
List<String> single(int id);
123153

124-
public List<String> multiple(int id);
154+
List<String> multiple(int id);
125155
}
126156

127157

@@ -142,7 +172,7 @@ public List<String> single(int id) {
142172
@Override
143173
@Caching(cacheable = {
144174
@Cacheable(cacheNames = "bigCache", unless = "#result.size() < 4"),
145-
@Cacheable(cacheNames = "smallCache", unless = "#result.size() > 3") })
175+
@Cacheable(cacheNames = "smallCache", unless = "#result.size() > 3")})
146176
public List<String> multiple(int id) {
147177
if (this.multipleCount > 0) {
148178
fail("Called too many times");
@@ -215,4 +245,49 @@ public Object getNeverCache(String key) {
215245
}
216246
}
217247

248+
@Configuration
249+
@EnableCaching
250+
public static class Spr13081Config extends CachingConfigurerSupport {
251+
252+
@Bean
253+
@Override
254+
public CacheResolver cacheResolver() {
255+
return new MyCacheResolver();
256+
}
257+
258+
@Bean
259+
public Spr13081Service service() {
260+
return new Spr13081Service();
261+
}
262+
263+
}
264+
265+
public static class MyCacheResolver extends AbstractCacheResolver {
266+
267+
public MyCacheResolver() {
268+
super(new ConcurrentMapCacheManager());
269+
}
270+
271+
@Override
272+
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
273+
String cacheName = (String) context.getArgs()[0];
274+
if (cacheName != null) {
275+
return Collections.singleton(cacheName);
276+
}
277+
return null;
278+
}
279+
280+
public Cache getCache(String name) {
281+
return getCacheManager().getCache(name);
282+
}
283+
}
284+
285+
public static class Spr13081Service {
286+
287+
@Cacheable
288+
public Object getSimple(String cacheName) {
289+
return new Object();
290+
}
291+
}
292+
218293
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,12 @@ public void fullClassLevelWithCustomCacheResolver() {
191191
}
192192

193193
@Test
194-
public void validateAtLeastOneCacheNameMustBeSet() {
195-
thrown.expect(IllegalStateException.class);
196-
getOps(AnnotatedClass.class, "noCacheNameSpecified");
194+
public void validateNoCacheIsValid() {
195+
// Valid as a CacheResolver might return the cache names to use with other info
196+
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "noCacheNameSpecified");
197+
CacheOperation cacheOperation = ops.iterator().next();
198+
assertNotNull("cache names set must not be null", cacheOperation.getCacheNames());
199+
assertEquals("no cache names specified", 0, cacheOperation.getCacheNames().size());
197200
}
198201

199202
@Test

0 commit comments

Comments
 (0)