Skip to content

Commit daf12a9

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 b5bb26b commit daf12a9

File tree

3 files changed

+85
-11
lines changed

3 files changed

+85
-11
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
@@ -235,11 +235,6 @@ private void validateCacheOperation(AnnotatedElement ae, CacheOperation operatio
235235
"default cache resolver if none is set. If a cache resolver is set, the cache manager" +
236236
"won't be used.");
237237
}
238-
if (operation.getCacheNames().isEmpty()) {
239-
throw new IllegalStateException("No cache names could be detected on '" +
240-
ae.toString() + "'. Make sure to set the value parameter on the annotation or " +
241-
"declare a @CacheConfig at the class-level with the default cache name(s) to use.");
242-
}
243238
}
244239

245240
@Override

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

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +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;
26+
import org.junit.rules.ExpectedException;
2427
import org.mockito.Mockito;
2528

2629
import org.springframework.cache.annotation.Cacheable;
2730
import org.springframework.cache.annotation.Caching;
31+
import org.springframework.cache.annotation.CachingConfigurerSupport;
2832
import org.springframework.cache.annotation.EnableCaching;
2933
import org.springframework.cache.concurrent.ConcurrentMapCache;
3034
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;
3138
import org.springframework.cache.support.SimpleCacheManager;
3239
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3340
import org.springframework.context.annotation.Bean;
@@ -45,6 +52,9 @@
4552
*/
4653
public class CacheReproTests {
4754

55+
@Rule
56+
public final ExpectedException thrown = ExpectedException.none();
57+
4858
@Test
4959
public void spr11124() throws Exception {
5060
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11124Config.class);
@@ -83,7 +93,7 @@ public void spr11592GetSimple() {
8393
}
8494

8595
@Test
86-
public void spr11592GetNeverCache(){
96+
public void spr11592GetNeverCache() {
8797
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Spr11592Config.class);
8898
Spr11592Service bean = context.getBean(Spr11592Service.class);
8999
Cache cache = context.getBean("cache", Cache.class);
@@ -99,6 +109,27 @@ public void spr11592GetNeverCache(){
99109
context.close();
100110
}
101111

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+
102133

103134
@Configuration
104135
@EnableCaching
@@ -118,9 +149,9 @@ public Spr11124Service service() {
118149

119150
public interface Spr11124Service {
120151

121-
public List<String> single(int id);
152+
List<String> single(int id);
122153

123-
public List<String> multiple(int id);
154+
List<String> multiple(int id);
124155
}
125156

126157

@@ -214,4 +245,49 @@ public Object getNeverCache(String key) {
214245
}
215246
}
216247

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+
217293
}

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)