To be implemented by classes annotated with @{@link org.springframework.cache.annotation.EnableCaching} - * that wish or need to specify explicitly the {@link CacheResolver} bean(s) to be used for - * annotation-driven cache management. * - *
See @{@link org.springframework.cache.annotation.EnableCaching} for general examples and - * context; see {@link #cacheResolver()} and {@link #exceptionCacheResolver()} for detailed + *
To be implemented by classes annotated with + * @{@link org.springframework.cache.annotation.EnableCaching} that wish or + * need to specify explicitly how exception caches are resolved for + * annotation-driven cache management. Consider extending {@link JCacheConfigurerSupport}, + * which provides a stub implementation of all interface methods. + * + *
See @{@link org.springframework.cache.annotation.EnableCaching} for + * general examples and context; see {@link #exceptionCacheResolver()} for detailed * instructions. * * @author Stephane Nicoll * @since 4.1 * @see CachingConfigurer + * @see JCacheConfigurerSupport * @see org.springframework.cache.annotation.EnableCaching */ public interface JCacheConfigurer extends CachingConfigurer { - /** - * Return the {@link CacheResolver} bean to use to resolve regular caches for - * annotation-driven cache management. Implementations must explicitly declare - * {@link org.springframework.context.annotation.Bean @Bean}, e.g. - *
- * @Configuration
- * @EnableCaching
- * public class AppConfig implements JCacheConfigurer {
- * @Bean // important!
- * @Override
- * public CacheResolver cacheResolver() {
- * // configure and return CacheResolver instance
- * }
- * // ...
- * }
- *
- * See {@link org.springframework.cache.annotation.EnableCaching} for more complete examples.
- */
- CacheResolver cacheResolver();
-
/**
* Return the {@link CacheResolver} bean to use to resolve exception caches for
* annotation-driven cache management. Implementations must explicitly declare
@@ -64,7 +47,7 @@ public interface JCacheConfigurer extends CachingConfigurer {
*
* @Configuration
* @EnableCaching
- * public class AppConfig implements JCacheConfigurer {
+ * public class AppConfig extends JCacheConfigurerSupport {
* @Bean // important!
* @Override
* public CacheResolver exceptionCacheResolver() {
diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java
new file mode 100644
index 00000000000..cd6adc7ef8f
--- /dev/null
+++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/config/JCacheConfigurerSupport.java
@@ -0,0 +1,41 @@
+/*
+ * 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.jcache.config;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.interceptor.CacheResolver;
+
+/**
+ * An extension of {@link CachingConfigurerSupport} that also implements
+ * {@link JCacheConfigurer}.
+ *
+ * Users of JSR-107 annotations may extend from this class rather than
+ * implementing from {@link JCacheConfigurer} directly.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see JCacheConfigurer
+ * @see CachingConfigurerSupport
+ */
+public class JCacheConfigurerSupport extends CachingConfigurerSupport implements JCacheConfigurer {
+
+ @Override
+ public CacheResolver exceptionCacheResolver() {
+ return null;
+ }
+
+}
diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/SimpleExceptionCacheResolver.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/SimpleExceptionCacheResolver.java
index edcab3a14d3..0809e13bc40 100644
--- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/SimpleExceptionCacheResolver.java
+++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/SimpleExceptionCacheResolver.java
@@ -19,13 +19,12 @@ package org.springframework.cache.jcache.interceptor;
import java.util.Collection;
import java.util.Collections;
-import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.BaseCacheResolver;
import org.springframework.cache.interceptor.BasicCacheOperation;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.jcache.model.CacheResultOperation;
-import org.springframework.util.Assert;
/**
* A simple {@link CacheResolver} that resolves the exception cache
@@ -36,16 +35,14 @@ import org.springframework.util.Assert;
* @since 4.1
* @see org.springframework.cache.jcache.model.CacheResultOperation#getExceptionCacheName()
*/
-public class SimpleExceptionCacheResolver implements CacheResolver {
-
- private final CacheManager cacheManager;
+public class SimpleExceptionCacheResolver extends BaseCacheResolver {
public SimpleExceptionCacheResolver(CacheManager cacheManager) {
- this.cacheManager = cacheManager;
+ super(cacheManager);
}
@Override
- public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
+ protected Collection getCacheNames(CacheOperationInvocationContext> context) {
BasicCacheOperation operation = context.getOperation();
if (!(operation instanceof CacheResultOperation)) {
throw new IllegalStateException("Could not extract exception cache name from " + operation);
@@ -53,9 +50,7 @@ public class SimpleExceptionCacheResolver implements CacheResolver {
CacheResultOperation cacheResultOperation = (CacheResultOperation) operation;
String exceptionCacheName = cacheResultOperation.getExceptionCacheName();
if (exceptionCacheName != null) {
- Cache cache = cacheManager.getCache(exceptionCacheName);
- Assert.notNull(cache, "Cannot find cache named '" + exceptionCacheName + "' for " + operation);
- return Collections.singleton(cache);
+ return Collections.singleton(exceptionCacheName);
}
return null;
}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java
index a15b8771149..8bf177b5314 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/config/JCacheJavaConfigTests.java
@@ -26,15 +26,19 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
+import org.springframework.cache.config.SomeKeyGenerator;
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.interceptor.SimpleKeyGenerator;
import org.springframework.cache.jcache.interceptor.AnnotatedJCacheableService;
import org.springframework.cache.jcache.interceptor.DefaultJCacheOperationSource;
+import org.springframework.cache.jcache.interceptor.SimpleExceptionCacheResolver;
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;
@@ -61,6 +65,35 @@ public class JCacheJavaConfigTests extends AbstractJCacheAnnotationTests {
cos.getDefaultExceptionCacheResolver());
}
+ @Test
+ public void emptyConfigSupport() {
+ ConfigurableApplicationContext context =
+ new AnnotationConfigApplicationContext(EmptyConfigSupportConfig.class);
+
+ DefaultJCacheOperationSource cos = context.getBean(DefaultJCacheOperationSource.class);
+ assertNotNull(cos.getDefaultCacheResolver());
+ assertEquals(SimpleCacheResolver.class, cos.getDefaultCacheResolver().getClass());
+ assertSame(context.getBean(CacheManager.class),
+ ((SimpleCacheResolver) cos.getDefaultCacheResolver()).getCacheManager());
+ assertNotNull(cos.getDefaultExceptionCacheResolver());
+ assertEquals(SimpleExceptionCacheResolver.class, cos.getDefaultExceptionCacheResolver().getClass());
+ assertSame(context.getBean(CacheManager.class),
+ ((SimpleExceptionCacheResolver) cos.getDefaultExceptionCacheResolver()).getCacheManager());
+ context.close();
+ }
+
+ @Test
+ public void bothSetOnlyResolverIsUsed() {
+ ConfigurableApplicationContext context =
+ new AnnotationConfigApplicationContext(FullCachingConfigSupport.class);
+
+ DefaultJCacheOperationSource cos = context.getBean(DefaultJCacheOperationSource.class);
+ assertSame(context.getBean("cacheResolver"), cos.getDefaultCacheResolver());
+ assertSame(context.getBean("keyGenerator"), cos.getDefaultKeyGenerator());
+ assertSame(context.getBean("exceptionCacheResolver"), cos.getDefaultExceptionCacheResolver());
+ context.close();
+ }
+
@Configuration
@EnableCaching
@@ -118,4 +151,42 @@ public class JCacheJavaConfigTests extends AbstractJCacheAnnotationTests {
}
}
+ @Configuration
+ @EnableCaching
+ public static class EmptyConfigSupportConfig extends JCacheConfigurerSupport {
+ @Bean
+ public CacheManager cm() {
+ return new NoOpCacheManager();
+ }
+ }
+
+ @Configuration
+ @EnableCaching
+ static class FullCachingConfigSupport extends JCacheConfigurerSupport {
+
+ @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");
+ }
+
+ @Override
+ @Bean
+ public CacheResolver exceptionCacheResolver() {
+ return new NamedCacheResolver(cacheManager(), "exception");
+ }
+ }
+
}
diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
index 92b2f38d549..1b980c08ca2 100644
--- a/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
+++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/interceptor/JCacheInterceptorTests.java
@@ -19,18 +19,14 @@ package org.springframework.cache.jcache.interceptor;
import static org.junit.Assert.*;
import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
import org.junit.Test;
-import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
-import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.cache.jcache.AbstractJCacheTests;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.util.ReflectionUtils;
@@ -45,7 +41,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
@Test
public void severalCachesNotSupported() {
JCacheInterceptor interceptor = createInterceptor(createOperationSource(
- cacheManager, new TestCacheResolver("default", "exception"),
+ cacheManager, new NamedCacheResolver(cacheManager, "default", "simpleCache"),
defaultExceptionCacheResolver, defaultKeyGenerator));
AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
@@ -65,7 +61,7 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
@Test
public void noCacheCouldBeResolved() {
JCacheInterceptor interceptor = createInterceptor(createOperationSource(
- cacheManager, new TestCacheResolver(), // Returns empty list
+ cacheManager, new NamedCacheResolver(cacheManager), // Returns empty list
defaultExceptionCacheResolver, defaultKeyGenerator));
AnnotatedJCacheableService service = new AnnotatedJCacheableService(cacheManager.getCache("default"));
@@ -132,25 +128,6 @@ public class JCacheInterceptorTests extends AbstractJCacheTests {
return interceptor;
}
-
- private class TestCacheResolver implements CacheResolver {
-
- private final String[] cacheNames;
-
- private TestCacheResolver(String... cacheNames) {
- this.cacheNames = cacheNames;
- }
-
- @Override
- public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
- List result = new ArrayList();
- for (String cacheName : cacheNames) {
- result.add(cacheManager.getCache(cacheName));
- }
- return result;
- }
- }
-
private static class DummyInvoker implements CacheOperationInvoker {
private final Object result;
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
index c9f88bd7787..47efe817da9 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/AbstractCachingConfiguration.java
@@ -22,6 +22,7 @@ import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
@@ -46,6 +47,8 @@ public abstract class AbstractCachingConfiguration
protected CacheManager cacheManager;
+ protected CacheResolver cacheResolver;
+
protected KeyGenerator keyGenerator;
@Autowired(required=false)
@@ -86,7 +89,7 @@ public abstract class AbstractCachingConfiguration
C cachingConfigurer = cachingConfigurers.iterator().next();
useCachingConfigurer(cachingConfigurer);
}
- else if (!CollectionUtils.isEmpty(cacheManagerBeans)) {
+ if (this.cacheManager == null && !CollectionUtils.isEmpty(cacheManagerBeans)) {
int nManagers = cacheManagerBeans.size();
if (nManagers > 1) {
throw new IllegalStateException(nManagers + " beans of type CacheManager " +
@@ -98,7 +101,7 @@ public abstract class AbstractCachingConfiguration
this.cacheManager = cacheManagerBeans.iterator().next();
// keyGenerator remains null; will fall back to default within CacheInterceptor
}
- else {
+ if (this.cacheManager == null) {
throw new IllegalStateException("No bean of type CacheManager could be found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation " +
"from your configuration.");
@@ -110,6 +113,7 @@ public abstract class AbstractCachingConfiguration
*/
protected void useCachingConfigurer(C config) {
this.cacheManager = config.cacheManager();
+ this.cacheResolver = config.cacheResolver();
this.keyGenerator = config.keyGenerator();
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java
index 0c30d960253..6446e5ae47c 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheConfig.java
@@ -53,10 +53,21 @@ public @interface CacheConfig {
String keyGenerator() default "";
/**
- * The bean name of the custom {@link org.springframework.cache.CacheManager} to use.
- * If none is set at the operation level, this one is used instead of the default.
+ * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
+ * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
+ * is set already.
+ *
If no resolver and no cache manager are set at the operation level, and no cache
+ * resolver is set on this instance, this one is used instead of the default.
+ * @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";
+
+ /**
+ * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
+ *
If no resolver and no cache manager are set at the operation level, this one is used
+ * instead of the default.
+ */
+ String cacheResolver() default "";
}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
index 5a4529b8e77..fcc3e443908 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java
@@ -59,10 +59,19 @@ public @interface CacheEvict {
String keyGenerator() default "";
/**
- * The bean name of the custom {@link org.springframework.cache.CacheManager} to use.
+ * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
+ * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
+ * is set already.
+ *
Mutually exclusive with the {@link #cacheResolver()} attribute.
+ * @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";
+ /**
+ * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
+ */
+ String cacheResolver() default "";
+
/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
*
Default is "", meaning the method is always cached.
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
index 81f7f463bd2..13b94c4f8f2 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java
@@ -64,10 +64,19 @@ public @interface CachePut {
String keyGenerator() default "";
/**
- * The bean name of the custom {@link org.springframework.cache.CacheManager} to use.
+ * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
+ * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
+ * is set already.
+ *
Mutually exclusive with the {@link #cacheResolver()} attribute.
+ * @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";
+ /**
+ * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
+ */
+ String cacheResolver() default "";
+
/**
* Spring Expression Language (SpEL) attribute used for conditioning the cache update.
*
Default is "", meaning the method result is always cached.
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
index 64eefc14633..3e871086b76 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -62,10 +62,19 @@ public @interface Cacheable {
String keyGenerator() default "";
/**
- * The bean name of the custom {@link org.springframework.cache.CacheManager} to use.
+ * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
+ * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
+ * is set already.
+ *
Mutually exclusive with the {@link #cacheResolver()} attribute.
+ * @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";
+ /**
+ * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
+ */
+ String cacheResolver() default "";
+
/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
*
Default is "", meaning the method is always cached.
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java
index b3dd6224c59..524e9ad901e 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurer.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-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.
@@ -17,31 +17,39 @@
package org.springframework.cache.annotation;
import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
/**
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableCaching} that wish or need to
- * specify explicitly the {@link CacheManager} and {@link KeyGenerator} beans to be used
- * for annotation-driven cache management.
+ * specify explicitly how caches are resolved and how keys are generated for annotation-driven
+ * cache management. Consider extending {@link CachingConfigurerSupport}, which provides a
+ * stub implementation of all interface methods.
*
*
See @{@link EnableCaching} for general examples and context; see
- * {@link #cacheManager()} and {@link #keyGenerator()} for detailed instructions.
+ * {@link #cacheManager()}, {@link #cacheResolver()} and {@link #keyGenerator()}
+ * for detailed instructions.
*
* @author Chris Beams
* @since 3.1
* @see EnableCaching
+ * @see CachingConfigurerSupport
*/
public interface CachingConfigurer {
/**
- * Return the cache manager bean to use for annotation-driven cache management.
- * Implementations must explicitly declare
+ * Return the cache manager bean to use for annotation-driven cache
+ * management. A default {@link CacheResolver} will be initialized
+ * behind the scene with this cache manager. For more fine-grained
+ * management of the cache resolution, consider setting the
+ * {@link CacheResolver} directly.
+ *
Implementations must explicitly declare
* {@link org.springframework.context.annotation.Bean @Bean}, e.g.
*
* @Configuration
* @EnableCaching
- * public class AppConfig implements CachingConfigurer {
+ * public class AppConfig extends CachingConfigurerSupport {
* @Bean // important!
* @Override
* public CacheManager cacheManager() {
@@ -54,6 +62,28 @@ public interface CachingConfigurer {
*/
CacheManager cacheManager();
+ /**
+ * Return the {@link CacheResolver} bean to use to resolve regular caches for
+ * annotation-driven cache management. This is an alternative option to set
+ * the {@link CacheManager} to use.
+ * Implementations must explicitly declare
+ * {@link org.springframework.context.annotation.Bean @Bean}, e.g.
+ *
+ * @Configuration
+ * @EnableCaching
+ * public class AppConfig extends CachingConfigurerSupport {
+ * @Bean // important!
+ * @Override
+ * public CacheResolver cacheResolver() {
+ * // configure and return CacheResolver instance
+ * }
+ * // ...
+ * }
+ *
+ * See {@link EnableCaching} for more complete examples.
+ */
+ CacheResolver cacheResolver();
+
/**
* Return the key generator bean to use for annotation-driven cache management.
* Implementations must explicitly declare
@@ -61,7 +91,7 @@ public interface CachingConfigurer {
*
* @Configuration
* @EnableCaching
- * public class AppConfig implements CachingConfigurer {
+ * public class AppConfig extends CachingConfigurerSupport {
* @Bean // important!
* @Override
* public KeyGenerator keyGenerator() {
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java
new file mode 100644
index 00000000000..29801d6633c
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/CachingConfigurerSupport.java
@@ -0,0 +1,48 @@
+/*
+ * 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.annotation;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.KeyGenerator;
+
+/**
+ * An implementation of {@link CachingConfigurer} with empty methods allowing
+ * sub-classes to override only the methods they're interested in.
+ *
+ * @author Stephane Nicoll
+ * @since 4.1
+ * @see CachingConfigurer
+ */
+public class CachingConfigurerSupport implements CachingConfigurer {
+
+ @Override
+ public CacheManager cacheManager() {
+ return null;
+ }
+
+ @Override
+ public KeyGenerator keyGenerator() {
+ return null;
+ }
+
+ @Override
+ public CacheResolver cacheResolver() {
+ return null;
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
index 1e1fbc52d09..aeb03c368ed 100644
--- a/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
+++ b/spring-context/src/main/java/org/springframework/cache/annotation/ProxyCachingConfiguration.java
@@ -59,7 +59,10 @@ public class ProxyCachingConfiguration extends AbstractCachingConfiguration target) {
final CacheConfig annotation = AnnotationUtils.getAnnotation(target, CacheConfig.class);
if (annotation != null) {
- return new DefaultCacheConfig(annotation.cacheManager(),
- annotation.keyGenerator(), annotation.cacheNames());
+ return new DefaultCacheConfig(annotation.cacheNames(), annotation.keyGenerator(),
+ annotation.cacheManager(), annotation.cacheResolver());
}
return new DefaultCacheConfig();
}
@@ -228,9 +231,16 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
+ if (StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(operation.getCacheResolver())) {
+ throw new IllegalStateException("Invalid cache annotation configuration on '"
+ + ae.toString() + "'. Both 'cacheManager' and 'cacheResolver' attributes have been set. " +
+ "These attributes are mutually exclusive: the cache manager is used to configure a" +
+ "default cache resolver if none is set. If a cache resolver is set, the cache manager" +
+ "won't be used.");
+ }
if (operation.getCacheNames().isEmpty()) {
throw new IllegalStateException("No cache names could be detected on '"
- + ae.toString()+ "'. Make sure to set the value parameter on the annotation or" +
+ + ae.toString() + "'. Make sure to set the value parameter on the annotation or " +
"declare a @CacheConfig at the class-level with the default cache name(s) to use.");
}
}
@@ -245,22 +255,29 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return SpringCacheAnnotationParser.class.hashCode();
}
+
/**
* Provides default settings for a given set of cache operations.
*/
static class DefaultCacheConfig {
- private final String cacheManager;
- private final String keyGenerator;
private final String[] cacheNames;
- private DefaultCacheConfig(String cacheManager, String keyGenerator, String[] cacheNames) {
- this.cacheManager = cacheManager;
- this.keyGenerator = keyGenerator;
+ private final String keyGenerator;
+
+ private final String cacheManager;
+
+ private final String cacheResolver;
+
+ private DefaultCacheConfig(String[] cacheNames, String keyGenerator,
+ String cacheManager, String cacheResolver) {
this.cacheNames = cacheNames;
+ this.keyGenerator = keyGenerator;
+ this.cacheManager = cacheManager;
+ this.cacheResolver = cacheResolver;
}
public DefaultCacheConfig() {
- this(null, null, null);
+ this(null, null, null, null);
}
/**
@@ -269,17 +286,29 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
* @param operation the operation to update
*/
public void applyDefault(CacheOperation operation) {
- if (!StringUtils.hasText(operation.getCacheManager()) && StringUtils.hasText(cacheManager)) {
- operation.setCacheManager(cacheManager);
+ if (operation.getCacheNames().isEmpty() && cacheNames != null) {
+ operation.setCacheNames(cacheNames);
}
if (!StringUtils.hasText(operation.getKey()) && !StringUtils.hasText(operation.getKeyGenerator())
&& StringUtils.hasText(keyGenerator)) {
operation.setKeyGenerator(keyGenerator);
}
- if (operation.getCacheNames().isEmpty() && cacheNames != null) {
- operation.setCacheNames(cacheNames);
+
+ if (isSet(operation.getCacheManager()) || isSet(operation.getCacheResolver())) {
+ // One of these is set so we should not inherit anything
+ }
+ else if (isSet(cacheResolver)) {
+ operation.setCacheResolver(cacheResolver);
+ }
+ else if (isSet(cacheManager)) {
+ operation.setCacheManager(cacheManager);
}
}
+
+ private boolean isSet(String s) {
+ return StringUtils.hasText(s);
+ }
+
}
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/BaseCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/BaseCacheResolver.java
new file mode 100644
index 00000000000..43010a73d2b
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/BaseCacheResolver.java
@@ -0,0 +1,92 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.util.Assert;
+
+/**
+ * A base {@link CacheResolver} implementation that requires the concrete
+ * implementation to provide the collection of cache name(s) based on the
+ * invocation context.
+ *
+ * @author Stephane Nicoll
+ */
+public abstract class BaseCacheResolver implements CacheResolver, InitializingBean {
+
+ private CacheManager cacheManager;
+
+ protected BaseCacheResolver(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ protected BaseCacheResolver() {
+ }
+
+ /**
+ * Set the {@link CacheManager} that this instance should use.
+ */
+ public void setCacheManager(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ /**
+ * Return the {@link CacheManager} that this instance use.
+ */
+ public CacheManager getCacheManager() {
+ return cacheManager;
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ Assert.notNull(cacheManager, "CacheManager must not be null");
+ }
+
+ @Override
+ public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
+ Collection cacheNames = getCacheNames(context);
+ if (cacheNames == null) {
+ return Collections.emptyList();
+ }
+ else {
+ Collection result = new ArrayList();
+ for (String cacheName : cacheNames) {
+ Cache cache = cacheManager.getCache(cacheName);
+ Assert.notNull(cache, "Cannot find cache named '" + cacheName + "' for " + context.getOperation());
+ result.add(cache);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * Provide the name of the cache(s) to resolve against the current cache manager.
+ * It is acceptable to return {@code null} to indicate that no cache could
+ * be resolved for this invocation.
+ *
+ * @param context the context of the particular invocation
+ * @return the cache name(s) to resolve or {@code null} if no cache should be resolved
+ */
+ protected abstract Collection getCacheNames(CacheOperationInvocationContext> context);
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
index 0f04f3f19dc..6fa24bf3388 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java
@@ -17,16 +17,15 @@
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
@@ -54,12 +53,12 @@ import org.springframework.util.StringUtils;
* Subclasses are responsible for calling methods in this class in
* the correct order.
*
- *
Uses the Strategy design pattern. A {@link CacheManager}
- * implementation will perform the actual cache management, and a
+ *
Uses the Strategy design pattern. A {@link CacheResolver}
+ * implementation will resolve the actual cache(s) to use, and a
* {@link CacheOperationSource} is used for determining caching
* operations.
*
- *
A cache aspect is serializable if its {@code CacheManager} and
+ *
A cache aspect is serializable if its {@code CacheResolver} and
* {@code CacheOperationSource} are serializable.
*
* @author Costin Leau
@@ -74,34 +73,25 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
protected final Log logger = LogFactory.getLog(getClass());
- private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
+ /**
+ * Cache of CacheOperationMetadata, keyed by {@link CacheOperationCacheKey}.
+ */
+ private final Map metadataCache =
+ new ConcurrentHashMap(1024);
- private CacheManager cacheManager;
+ private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
private CacheOperationSource cacheOperationSource;
private KeyGenerator keyGenerator = new SimpleKeyGenerator();
+ private CacheResolver cacheResolver;
+
private ApplicationContext applicationContext;
private boolean initialized = false;
- /**
- * Set the default {@link CacheManager} that this cache aspect should delegate to
- * if no specific cache manager has been set for the operation.
- */
- public void setCacheManager(CacheManager cacheManager) {
- this.cacheManager = cacheManager;
- }
-
- /**
- * Return the default {@link CacheManager} that this cache aspect delegates to.
- */
- public CacheManager getCacheManager() {
- return this.cacheManager;
- }
-
/**
* Set one or more cache operation sources which are used to find the cache
* attributes. If more than one source is provided, they will be aggregated using a
@@ -137,14 +127,46 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
return this.keyGenerator;
}
+ /**
+ * Set the {@link CacheManager} to use to create a default {@link CacheResolver}. Replace
+ * the current {@link CacheResolver}, if any.
+ *
+ * @see #setCacheResolver(CacheResolver)
+ * @see SimpleCacheResolver
+ */
+ public void setCacheManager(CacheManager cacheManager) {
+ this.cacheResolver = new SimpleCacheResolver(cacheManager);
+ }
+
+ /**
+ * Set the default {@link CacheResolver} that this cache aspect should delegate
+ * to if no specific cache resolver has been set for the operation.
+ * The default resolver resolves the caches against their names and the
+ * default cache manager.
+ * @see #setCacheManager(org.springframework.cache.CacheManager)
+ * @see SimpleCacheResolver
+ */
+ public void setCacheResolver(CacheResolver cacheResolver) {
+ Assert.notNull(cacheResolver);
+ this.cacheResolver = cacheResolver;
+ }
+
+ /**
+ * Return the default {@link CacheResolver} that this cache aspect delegates to.
+ */
+ public CacheResolver getCacheResolver() {
+ return cacheResolver;
+ }
+
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void afterPropertiesSet() {
- Assert.state(this.cacheManager != null, "Property 'cacheManager' is required");
- Assert.state(this.cacheOperationSource != null, "Property 'cacheOperationSources' is required: " +
+ Assert.state(this.cacheResolver != null, "'cacheResolver' is required. Either set the cache resolver " +
+ "to use or set the cache manager to create a default cache resolver based on it.");
+ Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSources' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect.");
Assert.state(this.applicationContext != null, "The application context was not injected as it should.");
this.initialized = true;
@@ -165,21 +187,85 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
return ClassUtils.getQualifiedMethodName(specificMethod);
}
- protected Collection extends Cache> getCaches(CacheOperation operation, CacheManager cacheManager) {
- Set cacheNames = operation.getCacheNames();
- Collection caches = new ArrayList(cacheNames.size());
- for (String cacheName : cacheNames) {
- Cache cache = cacheManager.getCache(cacheName);
- Assert.notNull(cache, "Cannot find cache named '" + cacheName + "' for " + operation);
- caches.add(cache);
+ protected Collection extends Cache> getCaches(CacheOperationInvocationContext context,
+ CacheResolver cacheResolver) {
+ Collection extends Cache> caches = cacheResolver.resolveCaches(context);
+ if (caches.isEmpty()) {
+ throw new IllegalStateException("No cache could be resolved for '" + context.getOperation()
+ + "' using resolver '" + cacheResolver
+ + "'. At least one cache should be provided per cache operation.");
}
return caches;
}
protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args,
- Object target, Class> targetClass) {
+ Object target, Class> targetClass) {
- return new CacheOperationContext(operation, method, args, target, targetClass);
+ CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
+ return new CacheOperationContext(metadata, args, target);
+ }
+
+ /**
+ * Return the {@link CacheOperationMetadata} for the specified operation.
+ * Resolve the {@link CacheResolver} and the {@link KeyGenerator} to be
+ * used for the operation.
+ * @param operation the operation
+ * @param method the method on which the operation is invoked
+ * @param targetClass the target type
+ * @return the resolved metadata for the operation
+ */
+ protected CacheOperationMetadata getCacheOperationMetadata(CacheOperation operation,
+ Method method, Class> targetClass) {
+ final CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
+ CacheOperationMetadata metadata = metadataCache.get(cacheKey);
+ if (metadata == null) {
+ KeyGenerator operationKeyGenerator;
+ if (StringUtils.hasText(operation.getKeyGenerator())) {
+ operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
+ }
+ else {
+ operationKeyGenerator = getKeyGenerator();
+ }
+ CacheResolver operationCacheResolver;
+ if (StringUtils.hasText(operation.getCacheResolver())) {
+ operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);
+ }
+ else if (StringUtils.hasText(operation.getCacheManager())) {
+ CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);
+ operationCacheResolver = new SimpleCacheResolver(cacheManager);
+ }
+ else {
+ operationCacheResolver = getCacheResolver();
+ }
+ metadata = new CacheOperationMetadata(operation, method, targetClass,
+ operationKeyGenerator, operationCacheResolver);
+ metadataCache.put(cacheKey, metadata);
+ }
+ return metadata;
+ }
+
+ /**
+ * Return a bean with the specified name and type. Used to resolve services that
+ * are referenced by name in a {@link CacheOperation}.
+ *
+ * @param beanName the name of the bean, as defined by the operation
+ * @param expectedType type type for the bean
+ * @return the bean matching that name
+ * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if such bean does not exist
+ * @see CacheOperation#keyGenerator
+ * @see CacheOperation#cacheManager
+ * @see CacheOperation#cacheResolver
+ */
+ protected T getBean(String beanName, Class expectedType) {
+ return BeanFactoryAnnotationUtils.qualifiedBeanOfType(
+ applicationContext, expectedType, beanName);
+ }
+
+ /**
+ * Clear the cached metadata.
+ */
+ protected void clearMetadataCache() {
+ metadataCache.clear();
}
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
@@ -245,7 +331,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
private void processCacheEvicts(Collection contexts, boolean beforeInvocation, Object result) {
for (CacheOperationContext context : contexts) {
- CacheEvictOperation operation = (CacheEvictOperation) context.operation;
+ CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
performCacheEvict(context, operation, result);
}
@@ -272,7 +358,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
private void logInvalidating(CacheOperationContext context, CacheEvictOperation operation, Object key) {
if (logger.isTraceEnabled()) {
logger.trace("Invalidating " + (key != null ? "cache key [" + key + "]" : "entire cache") +
- " for operation " + operation + " on method " + context.method);
+ " for operation " + operation + " on method " + context.metadata.method);
}
}
@@ -327,7 +413,8 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
private boolean isConditionPassing(CacheOperationContext context, Object result) {
boolean passing = context.isConditionPassing(result);
if (!passing && logger.isTraceEnabled()) {
- logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
+ logger.trace("Cache condition failed on method " + context.metadata.method +
+ " for operation " + context.metadata.operation);
}
return passing;
}
@@ -335,9 +422,9 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
private Object generateKey(CacheOperationContext context, Object result) {
Object key = context.generateKey(result);
Assert.notNull(key, "Null key returned for cache operation (maybe you are using named params " +
- "on classes without debug info?) " + context.operation);
+ "on classes without debug info?) " + context.metadata.operation);
if (logger.isTraceEnabled()) {
- logger.trace("Computed cache key " + key + " for operation " + context.operation);
+ logger.trace("Computed cache key " + key + " for operation " + context.metadata.operation);
}
return key;
}
@@ -349,7 +436,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
new LinkedMultiValueMap, CacheOperationContext>();
public CacheOperationContexts(Collection extends CacheOperation> operations,
- Method method, Object[] args, Object target, Class> targetClass) {
+ Method method, Object[] args, Object target, Class> targetClass) {
for (CacheOperation operation : operations) {
this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
@@ -358,51 +445,70 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
public Collection get(Class extends CacheOperation> operationClass) {
Collection result = this.contexts.get(operationClass);
- return (result != null ? result : Collections. emptyList());
+ return (result != null ? result : Collections.emptyList());
}
}
- protected class CacheOperationContext {
+ /**
+ * Metadata of a cache operation that does not depend on a particular invocation
+ * which makes it a good candidate for caching.
+ */
+ protected static class CacheOperationMetadata {
private final CacheOperation operation;
-
private final Method method;
+ private final Class> targetClass;
+ private final KeyGenerator keyGenerator;
+ private final CacheResolver cacheResolver;
+
+ public CacheOperationMetadata(CacheOperation operation, Method method,
+ Class> targetClass, KeyGenerator keyGenerator,
+ CacheResolver cacheResolver) {
+ this.operation = operation;
+ this.method = method;
+ this.targetClass = targetClass;
+ this.keyGenerator = keyGenerator;
+ this.cacheResolver = cacheResolver;
+ }
+ }
+
+ protected class CacheOperationContext implements CacheOperationInvocationContext {
+
+ private final CacheOperationMetadata metadata;
private final Object[] args;
private final Object target;
- private final Class> targetClass;
-
private final Collection extends Cache> caches;
- private final KeyGenerator operationKeyGenerator;
-
- private final CacheManager operationCacheManager;
-
- public CacheOperationContext(CacheOperation operation, Method method,
- Object[] args, Object target, Class> targetClass) {
- this.operation = operation;
- this.method = method;
- this.args = extractArgs(method, args);
+ public CacheOperationContext(CacheOperationMetadata metadata,
+ Object[] args, Object target) {
+ this.metadata = metadata;
+ this.args = extractArgs(metadata.method, args);
this.target = target;
- this.targetClass = targetClass;
- if (StringUtils.hasText(operation.getKeyGenerator())) { // TODO: exception mgt?
- this.operationKeyGenerator = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
- applicationContext, KeyGenerator.class, operation.getKeyGenerator());
- }
- else {
- this.operationKeyGenerator = keyGenerator;
- }
- if (StringUtils.hasText(operation.getCacheManager())) {
- this.operationCacheManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
- applicationContext, CacheManager.class, operation.getCacheManager());
- }
- else {
- this.operationCacheManager = cacheManager;
- }
- this.caches = CacheAspectSupport.this.getCaches(operation, operationCacheManager);
+ this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
+ }
+
+ @Override
+ public CacheOperation getOperation() {
+ return metadata.operation;
+ }
+
+ @Override
+ public Object getTarget() {
+ return target;
+ }
+
+ @Override
+ public Method getMethod() {
+ return metadata.method;
+ }
+
+ @Override
+ public Object[] getArgs() {
+ return args;
}
private Object[] extractArgs(Method method, Object[] args) {
@@ -417,24 +523,24 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
}
protected boolean isConditionPassing(Object result) {
- if (StringUtils.hasText(this.operation.getCondition())) {
+ if (StringUtils.hasText(this.metadata.operation.getCondition())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
- return evaluator.condition(this.operation.getCondition(), this.method, evaluationContext);
+ return evaluator.condition(this.metadata.operation.getCondition(), this.metadata.method, evaluationContext);
}
return true;
}
protected boolean canPutToCache(Object value) {
String unless = "";
- if (this.operation instanceof CacheableOperation) {
- unless = ((CacheableOperation) this.operation).getUnless();
+ if (this.metadata.operation instanceof CacheableOperation) {
+ unless = ((CacheableOperation) this.metadata.operation).getUnless();
}
- else if (this.operation instanceof CachePutOperation) {
- unless = ((CachePutOperation) this.operation).getUnless();
+ else if (this.metadata.operation instanceof CachePutOperation) {
+ unless = ((CachePutOperation) this.metadata.operation).getUnless();
}
if (StringUtils.hasText(unless)) {
EvaluationContext evaluationContext = createEvaluationContext(value);
- return !evaluator.unless(unless, this.method, evaluationContext);
+ return !evaluator.unless(unless, this.metadata.method, evaluationContext);
}
return true;
}
@@ -444,16 +550,16 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
* @return generated key (null if none can be generated)
*/
protected Object generateKey(Object result) {
- if (StringUtils.hasText(this.operation.getKey())) {
+ if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
- return evaluator.key(this.operation.getKey(), this.method, evaluationContext);
+ return evaluator.key(this.metadata.operation.getKey(), this.metadata.method, evaluationContext);
}
- return operationKeyGenerator.generate(this.target, this.method, this.args);
+ return metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(
- this.caches, this.method, this.args, this.target, this.targetClass, result);
+ this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
}
protected Collection extends Cache> getCaches() {
@@ -482,4 +588,31 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
}
}
+ private static class CacheOperationCacheKey {
+
+ private final CacheOperation cacheOperation;
+ private final MethodCacheKey methodCacheKey;
+
+ private CacheOperationCacheKey(CacheOperation cacheOperation, Method method, Class> targetClass) {
+ this.cacheOperation = cacheOperation;
+ this.methodCacheKey = new MethodCacheKey(method, targetClass);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CacheOperationCacheKey that = (CacheOperationCacheKey) o;
+ return cacheOperation.equals(that.cacheOperation)
+ && methodCacheKey.equals(that.methodCacheKey);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = cacheOperation.hashCode();
+ result = 31 * result + methodCacheKey.hashCode();
+ return result;
+ }
+ }
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
index 2e1787ec87b..5013eb84560 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheOperation.java
@@ -28,7 +28,7 @@ import org.springframework.util.Assert;
* @author Costin Leau
* @author Stephane Nicoll
*/
-public abstract class CacheOperation {
+public abstract class CacheOperation implements BasicCacheOperation {
private Set cacheNames = Collections.emptySet();
@@ -40,9 +40,12 @@ public abstract class CacheOperation {
private String cacheManager = "";
+ private String cacheResolver = "";
+
private String name = "";
+ @Override
public Set getCacheNames() {
return cacheNames;
}
@@ -63,6 +66,10 @@ public abstract class CacheOperation {
return cacheManager;
}
+ public String getCacheResolver() {
+ return cacheResolver;
+ }
+
public String getName() {
return name;
}
@@ -100,6 +107,11 @@ public abstract class CacheOperation {
this.cacheManager = cacheManager;
}
+ public void setCacheResolver(String cacheResolver) {
+ Assert.notNull(cacheManager);
+ this.cacheResolver = cacheResolver;
+ }
+
public void setName(String name) {
Assert.hasText(name);
this.name = name;
@@ -151,6 +163,8 @@ public abstract class CacheOperation {
result.append(this.keyGenerator);
result.append("' | cacheManager='");
result.append(this.cacheManager);
+ result.append("' | cacheResolver='");
+ result.append(this.cacheResolver);
result.append("' | condition='");
result.append(this.condition);
result.append("'");
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java b/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java
index e1076a2a9f3..9de16712115 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/MethodCacheKey.java
@@ -1,3 +1,19 @@
+/*
+ * 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 java.lang.reflect.Method;
@@ -21,7 +37,6 @@ public final class MethodCacheKey {
public MethodCacheKey(Method method, Class> targetClass) {
Assert.notNull(method, "method must be set.");
- Assert.notNull(targetClass, "targetClass must be set.");
this.method = method;
this.targetClass = targetClass;
}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
new file mode 100644
index 00000000000..8bdf94a7724
--- /dev/null
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/NamedCacheResolver.java
@@ -0,0 +1,56 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.springframework.cache.CacheManager;
+
+/**
+ * A {@link CacheResolver} that forces the resolution to a configurable
+ * collection of name(s) against a given {@link CacheManager}.
+ *
+ * @author Stephane Nicoll
+ */
+public class NamedCacheResolver extends BaseCacheResolver {
+
+ private Collection cacheNames;
+
+ public NamedCacheResolver(CacheManager cacheManager, String... cacheNames) {
+ super(cacheManager);
+ this.cacheNames = new ArrayList(Arrays.asList(cacheNames));
+ }
+
+ public NamedCacheResolver() {
+ }
+
+ /**
+ * Set the cache name(s) that this resolver should use.
+ */
+ public void setCacheNames(Collection cacheNames) {
+ this.cacheNames = cacheNames;
+ }
+
+ @Override
+ protected Collection getCacheNames(CacheOperationInvocationContext> context) {
+ return cacheNames;
+ }
+
+}
diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java
index a33f457d284..353de00c6f0 100644
--- a/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java
+++ b/spring-context/src/main/java/org/springframework/cache/interceptor/SimpleCacheResolver.java
@@ -16,39 +16,29 @@
package org.springframework.cache.interceptor;
-import java.util.ArrayList;
import java.util.Collection;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
-import org.springframework.util.Assert;
/**
* A simple {@link CacheResolver} that resolves the {@link Cache} instance(s)
* based on a configurable {@link CacheManager} and the name of the
- * cache(s): {@link BasicCacheOperation#getCacheNames()}
+ * cache(s) as provided by {@link BasicCacheOperation#getCacheNames() getCacheNames()}
*
* @author Stephane Nicoll
* @since 4.1
* @see BasicCacheOperation#getCacheNames()
*/
-public class SimpleCacheResolver implements CacheResolver {
-
- private final CacheManager cacheManager;
+public class SimpleCacheResolver extends BaseCacheResolver {
public SimpleCacheResolver(CacheManager cacheManager) {
- this.cacheManager = cacheManager;
+ super(cacheManager);
}
@Override
- public Collection extends Cache> resolveCaches(CacheOperationInvocationContext> context) {
- Collection result = new ArrayList();
- for (String cacheName : context.getOperation().getCacheNames()) {
- Cache cache = cacheManager.getCache(cacheName);
- Assert.notNull(cache, "Cannot find cache named '" + cacheName + "' for " + context.getOperation());
- result.add(cache);
- }
- return result;
+ protected Collection getCacheNames(CacheOperationInvocationContext> context) {
+ return context.getOperation().getCacheNames();
}
}
diff --git a/spring-context/src/test/java/org/springframework/cache/CacheTestUtils.java b/spring-context/src/test/java/org/springframework/cache/CacheTestUtils.java
new file mode 100644
index 00000000000..d50462d5ec9
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/cache/CacheTestUtils.java
@@ -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 caches = new ArrayList();
+ for (String cacheName : cacheNames) {
+ caches.add(new ConcurrentMapCache(cacheName));
+ }
+ result.setCaches(caches);
+ result.afterPropertiesSet();
+ return result;
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java b/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
index f8ae3ebb46a..89cf403dcc3 100644
--- a/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTests.java
@@ -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 ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
+ public void testCustomCacheResolver() {
+ Collection 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 ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
+ public void testCustomCacheResolverInherited() {
+ Collection 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 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 ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelKeyGenerator", 1);
+ CacheOperation cacheOperation = ops.iterator().next();
+ assertSharedConfig(cacheOperation, "custom", "", "classCacheResolver" , "classCacheName");
+ }
+
+ @Test
+ public void fullClassLevelWithCustomCacheManager() {
+ Collection ops = getOps(AnnotatedClassWithFullDefault.class, "methodLevelCacheManager", 1);
+ CacheOperation cacheOperation = ops.iterator().next();
+ assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
+ }
+
+ @Test
+ public void fullClassLevelWithCustomCacheResolver() {
+ Collection 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 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 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 ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheManager", 1);
+ CacheOperation cacheOperation = ops.iterator().next();
+ assertSharedConfig(cacheOperation, "classKeyGenerator", "custom", "", "classCacheName");
+ }
+
+ @Test
+ public void partialClassLevelWithCustomCacheResolver() {
+ Collection ops = getOps(AnnotatedClassWithSomeDefault.class, "methodLevelCacheResolver", 1);
+ CacheOperation cacheOperation = ops.iterator().next();
+ assertSharedConfig(cacheOperation, "classKeyGenerator", "", "custom", "classCacheName");
+ }
+
+ @Test
+ public void partialClassLevelWithNoCustomization() {
+ Collection 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 {
}
}
\ No newline at end of file
diff --git a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java
index 8a5450de836..92ec015de71 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/AbstractAnnotationTests.java
@@ -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();
diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTests.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTests.java
index b6785792a58..44c2ae593e5 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotationNamespaceDrivenTests.java
@@ -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");
}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/AnnotationTests.java b/spring-context/src/test/java/org/springframework/cache/config/AnnotationTests.java
index 04c07fd2083..8514c3ded2f 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/AnnotationTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/AnnotationTests.java
@@ -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");
}
+
}
\ No newline at end of file
diff --git a/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java b/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java
index e91a721b0c1..086f8fd1e03 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/CacheAdviceNamespaceTests.java
@@ -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());
}
+
}
diff --git a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java
index 73a11517017..cc0308a0b5b 100644
--- a/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java
+++ b/spring-context/src/test/java/org/springframework/cache/config/EnableCachingTests.java
@@ -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");
+ }
+ }
}
diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomisationTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomisationTests.java
new file mode 100644
index 00000000000..a74bce760ba
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/cache/interceptor/CacheResolverCustomisationTests.java
@@ -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).
+ * 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 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 getCacheNames(CacheOperationInvocationContext> context) {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java b/spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java
new file mode 100644
index 00000000000..19de44e84dc
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/cache/interceptor/MethodCacheKeyTests.java
@@ -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());
+ }
+
+}