Use CacheResolver in Spring abstraction

Prior to this commit, the CacheResolver was not used by Spring's
caching abstraction. This commit provides the necessary configuration
options to tune how a cache is resolved for a given operation.

CacheResolver can be customized globally, at the operation level or at
the class level. This breaks the CachingConfigurer class and a support
implementation is provided that implements all methods so that the
default is taken if it's not overridden. The JSR-107 support has been
updated as well, with a similar support class.

In particular, the static and runtime information of a cache
operation were mixed which prevents any forms of caching. As the
CacheResolver and the KeyGenerator can be customized, every operation
call lead to a lookup in the context for the bean.

This commit adds CacheOperationMetadata, a static holder of all
the non-runtime metadata about a cache operation. This is used
as an input source for the existing CacheOperationContext.

Caching the operation metadata in an AspectJ aspect can have side
effects as the aspect is static instance for the current ClassLoader.
The metadata cache needs to be cleared when the context shutdowns.
This is essentially a test issue only as in practice each application
runs in its class loader. Tests are now closing the context properly
to honor the DisposableBean callback.

Issue: SPR-11490
This commit is contained in:
Stephane Nicoll
2014-02-27 15:21:07 +01:00
parent 47a4327193
commit f3b8a4103e
32 changed files with 1333 additions and 245 deletions

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache;
import java.util.ArrayList;
import java.util.List;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
/**
* General cache-related test utilities.
*
* @author Stephane Nicoll
*/
public class CacheTestUtils {
/**
* Create a {@link SimpleCacheManager} with the specified cache(s).
* @param cacheNames the names of the caches to create
*/
public static CacheManager createSimpleCacheManager(String... cacheNames) {
SimpleCacheManager result = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
for (String cacheName : cacheNames) {
caches.add(new ConcurrentMapCache(cacheName));
}
result.setCaches(caches);
result.afterPropertiesSet();
return result;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2002-2014 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.
@@ -138,24 +138,55 @@ public class AnnotationCacheOperationSourceTests {
}
@Test
public void fullClassLevelWithCustomKeyManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
public void testCustomCacheResolver() {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheResolver", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "custom", "classCacheName");
assertEquals("Custom cache resolver not set", "custom", cacheOperation.getCacheResolver());
}
@Test
public void fullClassLevelWithCustomCacheManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
public void testCustomCacheResolverInherited() {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customCacheResolverInherited", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "custom", "classKeyGenerator", "classCacheName");
assertEquals("Custom cache resolver not set", "custom", cacheOperation.getCacheResolver());
}
@Test
public void testCacheResolverAndCacheManagerCannotBeSetTogether() {
try {
getOps(AnnotatedClass.class, "invalidCacheResolverAndCacheManagerSet");
fail("Should have failed to parse @Cacheable annotation");
} catch (IllegalStateException e) {
// expected
}
}
@Test
public void fullClassLevelWithCustomCacheName() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheName", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "classKeyGenerator", "custom");
assertSharedConfig(cacheOperation, "classKeyGenerator", "", "classCacheResolver", "custom");
}
@Test
public void fullClassLevelWithCustomKeyManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "custom", "", "classCacheResolver" , "classCacheName");
}
@Test
public void fullClassLevelWithCustomCacheManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
}
@Test
public void fullClassLevelWithCustomCacheResolver() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheResolver", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classKeyGenerator", "", "custom" , "classCacheName");
}
@Test
@@ -168,20 +199,42 @@ public class AnnotationCacheOperationSourceTests {
public void customClassLevelWithCustomCacheName() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithCustomDefault.class, "methodLevelCacheName", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classCacheManager", "classKeyGenerator", "custom");
assertSharedConfig(cacheOperation, "classKeyGenerator", "", "classCacheResolver", "custom");
}
@Test
public void severalCacheConfigUseClosest() {
Collection<CacheOperation> ops = getOps(MultipleCacheConfig.class, "multipleCacheConfig");
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "", "", "myCache");
assertSharedConfig(cacheOperation, "", "", "", "myCache");
}
private void assertSharedConfig(CacheOperation actual, String cacheManager,
String keyGenerator, String... cacheNames) {
assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager());
@Test
public void partialClassLevelWithCustomCacheManager() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheManager", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
}
@Test
public void partialClassLevelWithCustomCacheResolver() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheResolver", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classKeyGenerator", "", "custom", "classCacheName");
}
@Test
public void partialClassLevelWithNoCustomization() {
Collection<CacheOperation> ops = getOps(AnnotatedClassWithSomeDefault.class, "noCustomization", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertSharedConfig(cacheOperation, "classKeyGenerator", "classCacheManager", "", "classCacheName");
}
private void assertSharedConfig(CacheOperation actual, String keyGenerator, String cacheManager,
String cacheResolver, String... cacheNames) {
assertEquals("Wrong key manager", keyGenerator, actual.getKeyGenerator());
assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager());
assertEquals("Wrong cache resolver", cacheResolver, actual.getCacheResolver());
for (String cacheName : cacheNames) {
assertTrue("Cache '"+cacheName+"' not found (got "+actual.getCacheNames(),
actual.getCacheNames().contains(cacheName));
@@ -211,6 +264,10 @@ public class AnnotationCacheOperationSourceTests {
public void customCacheManager() {
}
@Cacheable(value = "test", cacheResolver = "custom")
public void customCacheResolver() {
}
@EvictFoo
public void singleStereotype() {
}
@@ -237,15 +294,28 @@ public class AnnotationCacheOperationSourceTests {
public void customCacheManagerInherited() {
}
@CacheableFooCustomCacheResolver
public void customCacheResolverInherited() {
}
@Cacheable(value = "test", cacheManager = "custom", cacheResolver = "custom")
public void invalidCacheResolverAndCacheManagerSet() {
}
@Cacheable // cache name can be inherited from CacheConfig. There's none here
public void noCacheNameSpecified() {
}
}
@CacheConfig(cacheNames = "classCacheName",
cacheManager = "classCacheManager", keyGenerator = "classKeyGenerator")
keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
private static class AnnotatedClassWithFullDefault {
@Cacheable("custom")
public void methodLevelCacheName() {
}
@Cacheable(keyGenerator = "custom")
public void methodLevelKeyGenerator() {
}
@@ -254,8 +324,8 @@ public class AnnotationCacheOperationSourceTests {
public void methodLevelCacheManager() {
}
@Cacheable("custom")
public void methodLevelCacheName() {
@Cacheable(cacheResolver = "custom")
public void methodLevelCacheResolver() {
}
}
@@ -267,6 +337,25 @@ public class AnnotationCacheOperationSourceTests {
}
}
@CacheConfig(cacheNames = "classCacheName",
keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager")
private static class AnnotatedClassWithSomeDefault {
@Cacheable(cacheManager = "custom")
public void methodLevelCacheManager() {
}
@Cacheable(cacheResolver = "custom")
public void methodLevelCacheResolver() {
}
@Cacheable
public void noCustomization() {
}
}
@CacheConfigFoo
@CacheConfig(cacheNames = "myCache") // multiple sources
private static class MultipleCacheConfig {
@@ -292,7 +381,12 @@ public class AnnotationCacheOperationSourceTests {
@Target(ElementType.METHOD)
@Cacheable(value = "foo", cacheManager = "custom")
public @interface CacheableFooCustomCacheManager {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Cacheable(value = "foo", cacheResolver = "custom")
public @interface CacheableFooCustomCacheResolver {
}
@Retention(RetentionPolicy.RUNTIME)
@@ -309,7 +403,8 @@ public class AnnotationCacheOperationSourceTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@CacheConfig(cacheManager = "classCacheManager", keyGenerator = "classKeyGenerator")
@CacheConfig(keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
public @interface CacheConfigFoo {
}
}

View File

@@ -22,13 +22,14 @@ import static org.junit.Assert.*;
import java.util.Collection;
import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Abstract annotation test (containing several reusable methods).
@@ -40,7 +41,7 @@ import org.springframework.context.ApplicationContext;
*/
public abstract class AbstractAnnotationTests {
protected ApplicationContext ctx;
protected ConfigurableApplicationContext ctx;
protected CacheableService<?> cs;
@@ -51,7 +52,7 @@ public abstract class AbstractAnnotationTests {
protected CacheManager customCm;
/** @return a refreshed application context */
protected abstract ApplicationContext getApplicationContext();
protected abstract ConfigurableApplicationContext getApplicationContext();
@Before
public void setup() {
@@ -67,6 +68,11 @@ public abstract class AbstractAnnotationTests {
assertTrue(cn.contains("primary"));
}
@After
public void tearDown() {
ctx.close();
}
public void testCacheable(CacheableService<?> service) throws Exception {
Object o1 = new Object();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 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.
@@ -20,7 +20,7 @@ import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
/**
@@ -30,7 +30,7 @@ import org.springframework.context.support.GenericXmlApplicationContext;
public class AnnotationNamespaceDrivenTests extends AbstractAnnotationTests {
@Override
protected ApplicationContext getApplicationContext() {
protected ConfigurableApplicationContext getApplicationContext() {
return new GenericXmlApplicationContext(
"/org/springframework/cache/config/annotationDrivenCacheNamespace.xml");
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2002-2014 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.
@@ -16,7 +16,7 @@
package org.springframework.cache.config;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
/**
@@ -26,8 +26,9 @@ import org.springframework.context.support.GenericXmlApplicationContext;
public class AnnotationTests extends AbstractAnnotationTests {
@Override
protected ApplicationContext getApplicationContext() {
protected ConfigurableApplicationContext getApplicationContext() {
return new GenericXmlApplicationContext(
"/org/springframework/cache/config/annotationDrivenCacheConfig.xml");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2011 the original author or authors.
* Copyright 2002-2014 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.
@@ -19,7 +19,7 @@ package org.springframework.cache.config;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
/**
@@ -29,7 +29,7 @@ import org.springframework.context.support.GenericXmlApplicationContext;
public class CacheAdviceNamespaceTests extends AbstractAnnotationTests {
@Override
protected ApplicationContext getApplicationContext() {
protected ConfigurableApplicationContext getApplicationContext() {
return new GenericXmlApplicationContext(
"/org/springframework/cache/config/cache-advice.xml");
}
@@ -39,4 +39,5 @@ public class CacheAdviceNamespaceTests extends AbstractAnnotationTests {
CacheInterceptor bean = ctx.getBean("cacheAdviceClass", CacheInterceptor.class);
Assert.assertSame(ctx.getBean("keyGenerator"), bean.getKeyGenerator());
}
}

View File

@@ -16,22 +16,21 @@
package org.springframework.cache.config;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.CacheTestUtils;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -46,7 +45,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
/** hook into superclass suite of tests */
@Override
protected ApplicationContext getApplicationContext() {
protected ConfigurableApplicationContext getApplicationContext() {
return new AnnotationConfigApplicationContext(EnableCachingConfig.class);
}
@@ -111,19 +110,38 @@ public class EnableCachingTests extends AbstractAnnotationTests {
}
}
@Test
public void emptyConfigSupport() {
ConfigurableApplicationContext context =
new AnnotationConfigApplicationContext(EmptyConfigSupportConfig.class);
CacheInterceptor ci = context.getBean(CacheInterceptor.class);
assertNotNull(ci.getCacheResolver());
assertEquals(SimpleCacheResolver.class, ci.getCacheResolver().getClass());
assertSame(context.getBean(CacheManager.class),
((SimpleCacheResolver)ci.getCacheResolver()).getCacheManager());
context.close();
}
@Test
public void bothSetOnlyResolverIsUsed() {
ConfigurableApplicationContext context =
new AnnotationConfigApplicationContext(FullCachingConfig.class);
CacheInterceptor ci = context.getBean(CacheInterceptor.class);
assertSame(context.getBean("cacheResolver"), ci.getCacheResolver());
assertSame(context.getBean("keyGenerator"), ci.getKeyGenerator());
context.close();
}
@Configuration
@EnableCaching
static class EnableCachingConfig implements CachingConfigurer {
static class EnableCachingConfig extends CachingConfigurerSupport {
@Override
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cm = new SimpleCacheManager();
cm.setCaches(Arrays.asList(
new ConcurrentMapCache("default"),
new ConcurrentMapCache("primary"),
new ConcurrentMapCache("secondary")));
return cm;
return CacheTestUtils.createSimpleCacheManager("default", "primary", "secondary");
}
@Bean
@@ -149,9 +167,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
@Bean
public CacheManager customCacheManager() {
SimpleCacheManager cm = new SimpleCacheManager();
cm.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cm;
return CacheTestUtils.createSimpleCacheManager("default");
}
}
@@ -182,7 +198,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
@Configuration
@EnableCaching
static class MultiCacheManagerConfigurer implements CachingConfigurer {
static class MultiCacheManagerConfigurer extends CachingConfigurerSupport {
@Bean
public CacheManager cm1() { return new NoOpCacheManager(); }
@Bean
@@ -197,4 +213,37 @@ public class EnableCachingTests extends AbstractAnnotationTests {
return null;
}
}
@Configuration
@EnableCaching
static class EmptyConfigSupportConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cm() {
return new NoOpCacheManager();
}
}
@Configuration
@EnableCaching
static class FullCachingConfig extends CachingConfigurerSupport {
@Override
@Bean
public CacheManager cacheManager() {
return new NoOpCacheManager();
}
@Override
@Bean
public KeyGenerator keyGenerator() {
return new SomeKeyGenerator();
}
@Override
@Bean
public CacheResolver cacheResolver() {
return new NamedCacheResolver(cacheManager(), "foo");
}
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.CacheTestUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ReflectionUtils;
/**
* Provides various {@link CacheResolver} customisations scenario
*
* @author Stephane Nicoll
*/
public class CacheResolverCustomisationTests {
private CacheManager cacheManager;
private CacheManager anotherCacheManager;
private SimpleService simpleService;
@Before
public void setUp() {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
this.cacheManager = context.getBean("cacheManager", CacheManager.class);
this.anotherCacheManager = context.getBean("anotherCacheManager", CacheManager.class);
this.simpleService = context.getBean(SimpleService.class);
}
@Test
public void noCustomization() {
Cache cache = cacheManager.getCache("default");
Object key = new Object();
assertCacheMiss(key, cache);
Object value = simpleService.getSimple(key);
assertCacheHit(key, value, cache);
}
@Test
public void customCacheResolver() {
Cache cache = cacheManager.getCache("primary");
Object key = new Object();
assertCacheMiss(key, cache);
Object value = simpleService.getWithCustomCacheResolver(key);
assertCacheHit(key, value, cache);
}
@Test
public void customCacheManager() {
Cache cache = anotherCacheManager.getCache("default");
Object key = new Object();
assertCacheMiss(key, cache);
Object value = simpleService.getWithCustomCacheManager(key);
assertCacheHit(key, value, cache);
}
@Test
public void runtimeResolution() {
Cache defaultCache = cacheManager.getCache("default");
Cache primaryCache = cacheManager.getCache("primary");
Object key = new Object();
assertCacheMiss(key, defaultCache, primaryCache);
Object value = simpleService.getWithRuntimeCacheResolution(key, "default");
assertCacheHit(key, value, defaultCache);
assertCacheMiss(key, primaryCache);
Object key2 = new Object();
assertCacheMiss(key2, defaultCache, primaryCache);
Object value2 = simpleService.getWithRuntimeCacheResolution(key2, "primary");
assertCacheHit(key2, value2, primaryCache);
assertCacheMiss(key2, defaultCache);
}
@Test
public void namedResolution() {
Cache cache = cacheManager.getCache("secondary");
Object key = new Object();
assertCacheMiss(key, cache);
Object value = simpleService.getWithNamedCacheResolution(key);
assertCacheHit(key, value, cache);
}
@Test
public void noCacheResolved() {
Method m = ReflectionUtils.findMethod(SimpleService.class, "noCacheResolved", Object.class);
try {
simpleService.noCacheResolved(new Object());
fail("Should have failed, no cache resolved");
} catch (IllegalStateException e) {
String msg = e.getMessage();
assertTrue("Reference to the method must be contained in the message", msg.contains(m.toString()));
}
}
@Test
public void unknownCacheResolver() {
try {
simpleService.unknownCacheResolver(new Object());
fail("Should have failed, no cache resolver with that name");
} catch (NoSuchBeanDefinitionException e) {
assertEquals("Wrong bean name in exception", "unknownCacheResolver", e.getBeanName());
}
}
protected void assertCacheMiss(Object key, Cache... caches) {
for (Cache cache : caches) {
assertNull("No entry in " + cache + " should have been found with key " + key, cache.get(key));
}
}
protected void assertCacheHit(Object key, Object value, Cache... caches) {
for (Cache cache : caches) {
Cache.ValueWrapper wrapper = cache.get(key);
assertNotNull("An entry in " + cache + " should have been found with key " + key, wrapper);
assertEquals("Wrong value in " + cache + " for entry with key " + key, value, wrapper.get());
}
}
@Configuration
@EnableCaching
static class Config extends CachingConfigurerSupport {
@Override
@Bean
public CacheManager cacheManager() {
return CacheTestUtils.createSimpleCacheManager("default", "primary", "secondary");
}
@Override
@Bean
public KeyGenerator keyGenerator() {
return null;
}
@Bean
public CacheManager anotherCacheManager() {
return CacheTestUtils.createSimpleCacheManager("default", "primary", "secondary");
}
@Bean
public CacheResolver primaryCacheResolver() {
return new NamedCacheResolver(cacheManager(), "primary");
}
@Bean
public CacheResolver secondaryCacheResolver() {
return new NamedCacheResolver(cacheManager(), "primary");
}
@Bean
public CacheResolver runtimeCacheResolver() {
return new RuntimeCacheResolver(cacheManager());
}
@Bean
public CacheResolver namedCacheResolver() {
NamedCacheResolver resolver = new NamedCacheResolver();
resolver.setCacheManager(cacheManager());
resolver.setCacheNames(Collections.singleton("secondary"));
return resolver;
}
@Bean
public CacheResolver nullCacheResolver() {
return new NullCacheResolver(cacheManager());
}
@Bean
public SimpleService simpleService() {
return new SimpleService();
}
}
@CacheConfig(cacheNames = "default")
static class SimpleService {
private final AtomicLong counter = new AtomicLong();
@Cacheable
public Object getSimple(Object key) {
return counter.getAndIncrement();
}
@Cacheable(cacheResolver = "primaryCacheResolver")
public Object getWithCustomCacheResolver(Object key) {
return counter.getAndIncrement();
}
@Cacheable(cacheManager = "anotherCacheManager")
public Object getWithCustomCacheManager(Object key) {
return counter.getAndIncrement();
}
@Cacheable(cacheResolver = "runtimeCacheResolver", key = "#p0")
public Object getWithRuntimeCacheResolution(Object key, String cacheName) {
return counter.getAndIncrement();
}
@Cacheable(cacheResolver = "namedCacheResolver")
public Object getWithNamedCacheResolution(Object key) {
return counter.getAndIncrement();
}
@Cacheable(cacheResolver = "nullCacheResolver") // No cache resolved for the operation
public Object noCacheResolved(Object key) {
return counter.getAndIncrement();
}
@Cacheable(cacheResolver = "unknownCacheResolver") // No such bean defined
public Object unknownCacheResolver(Object key) {
return counter.getAndIncrement();
}
}
/**
* Example of {@link CacheResolver} that resolve the caches at
* runtime (i.e. based on method invocation parameters).
* <p>Expects the second argument to hold the name of the cache to use
*/
private static class RuntimeCacheResolver extends BaseCacheResolver {
private RuntimeCacheResolver(CacheManager cacheManager) {
super(cacheManager);
}
@Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
String cacheName = (String) context.getArgs()[1];
return Collections.singleton(cacheName);
}
}
private static class NullCacheResolver extends BaseCacheResolver {
private NullCacheResolver(CacheManager cacheManager) {
super(cacheManager);
}
@Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
return null;
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.springframework.util.ReflectionUtils;
/**
* @author Stephane Nicoll
*/
public class MethodCacheKeyTests {
@Rule
public final TestName name = new TestName();
@Test
public void sameInstanceEquals() {
Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName());
MethodCacheKey instance = new MethodCacheKey(m, getClass());
assertKeyEquals(instance, instance);
}
@Test
public void equals() {
Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName());
MethodCacheKey first = new MethodCacheKey(m, getClass());
MethodCacheKey second = new MethodCacheKey(m, getClass());
assertKeyEquals(first, second);
}
@Test
public void equalsNoTarget() {
Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName());
MethodCacheKey first = new MethodCacheKey(m, null);
MethodCacheKey second = new MethodCacheKey(m, null);
assertKeyEquals(first, second);
}
@Test
public void noTargetClassNotEquals() {
Method m = ReflectionUtils.findMethod(getClass(), name.getMethodName());
MethodCacheKey first = new MethodCacheKey(m, getClass());
MethodCacheKey second = new MethodCacheKey(m, null);
assertFalse(first.equals(second));
}
protected void assertKeyEquals(MethodCacheKey first, MethodCacheKey second) {
assertEquals(first, second);
assertEquals(first.hashCode(), second.hashCode());
}
}