Cache provider related exceptions handling

This commit adds the necessary infrastructure to handle exceptions
thrown by a cache provider in both Spring's and JCache's caching
abstractions.

Both interceptors can be configured with a CacheErrorHandler that
defines several callbacks on typical cache operations. In particular,
handleCacheGetError can be implemented in such a way that an
exception thrown by the provider is handled as a cache miss by the
caching abstraction.

The handler can be configured with both CachingConfigurer and the
XML namespace (error-handler property)

Issue: SPR-9275
This commit is contained in:
Stephane Nicoll 2014-05-20 10:02:27 +02:00
parent c7d1c49d6d
commit 05e96ee448
30 changed files with 1158 additions and 32 deletions

View File

@ -55,6 +55,9 @@ public class ProxyJCacheConfiguration extends AbstractJCacheConfiguration {
public JCacheInterceptor cacheInterceptor() {
JCacheInterceptor interceptor = new JCacheInterceptor();
interceptor.setCacheOperationSource(cacheOperationSource());
if (this.errorHandler != null) {
interceptor.setErrorHandler(this.errorHandler);
}
return interceptor;
}

View File

@ -24,6 +24,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.AbstractCacheInvoker;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.jcache.model.BaseCacheOperation;
@ -36,10 +38,14 @@ import org.springframework.cache.jcache.model.BaseCacheOperation;
*/
@SuppressWarnings("serial")
public abstract class AbstractCacheInterceptor<O extends BaseCacheOperation<A>, A extends Annotation>
implements Serializable {
extends AbstractCacheInvoker implements Serializable {
protected final Log logger = LogFactory.getLog(getClass());
protected AbstractCacheInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
protected abstract Object invoke(CacheOperationInvocationContext<O> context,
CacheOperationInvoker invoker) throws Throwable;

View File

@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import javax.cache.annotation.CacheKeyInvocationContext;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.jcache.model.BaseKeyCacheOperation;
@ -34,6 +35,10 @@ import org.springframework.cache.jcache.model.BaseKeyCacheOperation;
public abstract class AbstractKeyCacheInterceptor<O extends BaseKeyCacheOperation<A>, A extends Annotation>
extends AbstractCacheInterceptor<O, A> {
protected AbstractKeyCacheInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
/**
* Generate a key for the specified invocation.
* @param context the context of the invocation

View File

@ -20,6 +20,7 @@ import javax.cache.annotation.CacheKeyInvocationContext;
import javax.cache.annotation.CachePut;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.jcache.model.CachePutOperation;
@ -33,6 +34,10 @@ import org.springframework.cache.jcache.model.CachePutOperation;
@SuppressWarnings("serial")
public class CachePutInterceptor extends AbstractKeyCacheInterceptor<CachePutOperation, CachePut> {
public CachePutInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
@Override
protected Object invoke(CacheOperationInvocationContext<CachePutOperation> context,
CacheOperationInvoker invoker) {
@ -65,7 +70,7 @@ public class CachePutInterceptor extends AbstractKeyCacheInterceptor<CachePutOpe
protected void cacheValue(CacheOperationInvocationContext<CachePutOperation> context, Object value) {
Object key = generateKey(context);
Cache cache = resolveCache(context);
cache.put(key, value);
doPut(cache, key, value);
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.cache.jcache.interceptor;
import javax.cache.annotation.CacheRemoveAll;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.jcache.model.CacheRemoveAllOperation;
@ -33,6 +34,10 @@ import org.springframework.cache.jcache.model.CacheRemoveAllOperation;
public class CacheRemoveAllInterceptor
extends AbstractCacheInterceptor<CacheRemoveAllOperation, CacheRemoveAll> {
protected CacheRemoveAllInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
@Override
protected Object invoke(CacheOperationInvocationContext<CacheRemoveAllOperation> context,
CacheOperationInvoker invoker) {
@ -66,7 +71,7 @@ public class CacheRemoveAllInterceptor
logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation "
+ context.getOperation());
}
cache.clear();
doClear(cache);
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.cache.jcache.interceptor;
import javax.cache.annotation.CacheRemove;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.jcache.model.CacheRemoveOperation;
@ -32,6 +33,10 @@ import org.springframework.cache.jcache.model.CacheRemoveOperation;
@SuppressWarnings("serial")
public class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<CacheRemoveOperation, CacheRemove> {
protected CacheRemoveEntryInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
@Override
protected Object invoke(CacheOperationInvocationContext<CacheRemoveOperation> context,
CacheOperationInvoker invoker) {
@ -66,7 +71,7 @@ public class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor<Cac
logger.trace("Invalidating key [" + key + "] on cache '" + cache.getName()
+ "' for operation " + context.getOperation());
}
cache.evict(key);
doEvict(cache, key);
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.cache.jcache.interceptor;
import javax.cache.annotation.CacheResult;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
import org.springframework.cache.interceptor.CacheResolver;
@ -35,6 +36,10 @@ import org.springframework.util.filter.ExceptionTypeFilter;
@SuppressWarnings("serial")
public class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheResultOperation, CacheResult> {
public CacheResultInterceptor(CacheErrorHandler errorHandler) {
super(errorHandler);
}
@Override
protected Object invoke(CacheOperationInvocationContext<CacheResultOperation> context,
CacheOperationInvoker invoker) {
@ -46,7 +51,7 @@ public class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheRes
Cache exceptionCache = resolveExceptionCache(context);
if (!operation.isAlwaysInvoked()) {
Cache.ValueWrapper cachedValue = cache.get(cacheKey);
Cache.ValueWrapper cachedValue = doGet(cache, cacheKey);
if (cachedValue != null) {
return cachedValue.get();
}
@ -74,7 +79,7 @@ public class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheRes
if (exceptionCache == null) {
return;
}
Cache.ValueWrapper result = exceptionCache.get(cacheKey);
Cache.ValueWrapper result = doGet(exceptionCache, cacheKey);
if (result != null) {
throw rewriteCallStack((Throwable) result.get(), getClass().getName(), "invoke");
}
@ -111,7 +116,6 @@ public class CacheResultInterceptor extends AbstractKeyCacheInterceptor<CacheRes
* @param exception the exception to merge with the current call stack
* @param className the class name of the common ancestor
* @param methodName the method name of the common ancestor
* @param <T> the type of the exception
* @return a clone exception with a rewritten call stack composed of the current
* call stack up to (included) the common ancestor specified by the {@code className} and
* {@code methodName} arguments, followed by stack trace elements of the specified

View File

@ -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.jcache.interceptor;
import java.lang.annotation.Annotation;
@ -8,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.interceptor.AbstractCacheInvoker;
import org.springframework.cache.interceptor.BasicCacheOperation;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheOperationInvoker;
@ -36,7 +53,7 @@ import org.springframework.util.Assert;
* @see KeyGeneratorAdapter
* @see CacheResolverAdapter
*/
public class JCacheAspectSupport implements InitializingBean {
public class JCacheAspectSupport extends AbstractCacheInvoker implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
@ -44,13 +61,13 @@ public class JCacheAspectSupport implements InitializingBean {
private boolean initialized = false;
private final CacheResultInterceptor cacheResultInterceptor = new CacheResultInterceptor();
private CacheResultInterceptor cacheResultInterceptor;
private final CachePutInterceptor cachePutInterceptor = new CachePutInterceptor();
private CachePutInterceptor cachePutInterceptor;
private final CacheRemoveEntryInterceptor cacheRemoveEntryInterceptor = new CacheRemoveEntryInterceptor();
private CacheRemoveEntryInterceptor cacheRemoveEntryInterceptor;
private final CacheRemoveAllInterceptor cacheRemoveAllInterceptor = new CacheRemoveAllInterceptor();
private CacheRemoveAllInterceptor cacheRemoveAllInterceptor;
public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) {
Assert.notNull(cacheOperationSource);
@ -67,6 +84,13 @@ public class JCacheAspectSupport implements InitializingBean {
public void afterPropertiesSet() {
Assert.state(this.cacheOperationSource != null, "The 'cacheOperationSource' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect.");
Assert.state(this.getErrorHandler() != null, "The 'errorHandler' is required.");
this.cacheResultInterceptor = new CacheResultInterceptor(getErrorHandler());
this.cachePutInterceptor = new CachePutInterceptor(getErrorHandler());
this.cacheRemoveEntryInterceptor = new CacheRemoveEntryInterceptor(getErrorHandler());
this.cacheRemoveAllInterceptor = new CacheRemoveAllInterceptor(getErrorHandler());
this.initialized = true;
}

View File

@ -43,6 +43,8 @@ public abstract class AbstractJCacheAnnotationTests {
@Rule
public final TestName name = new TestName();
protected ApplicationContext ctx;
private JCacheableService<?> service;
private CacheManager cacheManager;
@ -51,9 +53,9 @@ public abstract class AbstractJCacheAnnotationTests {
@Before
public void setUp() {
ApplicationContext context = getApplicationContext();
service = context.getBean(JCacheableService.class);
cacheManager = context.getBean("cacheManager", CacheManager.class);
ctx = getApplicationContext();
service = ctx.getBean(JCacheableService.class);
cacheManager = ctx.getBean("cacheManager", CacheManager.class);
}
@Test

View File

@ -27,13 +27,16 @@ 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.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.NamedCacheResolver;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
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.JCacheInterceptor;
import org.springframework.cache.jcache.interceptor.SimpleExceptionCacheResolver;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.cache.support.SimpleCacheManager;
@ -63,6 +66,8 @@ public class JCacheJavaConfigTests extends AbstractJCacheAnnotationTests {
cos.getDefaultCacheResolver());
assertSame(context.getBean("exceptionCacheResolver", CacheResolver.class),
cos.getDefaultExceptionCacheResolver());
JCacheInterceptor interceptor = context.getBean(JCacheInterceptor.class);
assertSame(context.getBean("errorHandler", CacheErrorHandler.class), interceptor.getErrorHandler());
}
@Test
@ -138,6 +143,12 @@ public class JCacheJavaConfigTests extends AbstractJCacheAnnotationTests {
return new SimpleKeyGenerator();
}
@Override
@Bean
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler();
}
@Override
@Bean
public CacheResolver cacheResolver() {

View File

@ -16,6 +16,12 @@
package org.springframework.cache.jcache.config;
import static org.junit.Assert.assertSame;
import org.junit.Test;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.jcache.interceptor.JCacheInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
@ -30,4 +36,10 @@ public class JCacheNamespaceDrivenTests extends AbstractJCacheAnnotationTests {
"/org/springframework/cache/jcache/config/jCacheNamespaceDriven.xml");
}
@Test
public void testCacheErrorHandler() {
JCacheInterceptor ci = ctx.getBean(JCacheInterceptor.class);
assertSame(ctx.getBean("errorHandler", CacheErrorHandler.class), ci.getErrorHandler());
}
}

View File

@ -0,0 +1,181 @@
/*
* 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.interceptor;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import javax.cache.annotation.CacheDefaults;
import javax.cache.annotation.CachePut;
import javax.cache.annotation.CacheRemove;
import javax.cache.annotation.CacheRemoveAll;
import javax.cache.annotation.CacheResult;
import javax.cache.annotation.CacheValue;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
*
* @author Stephane Nicoll
*/
public class JCacheErrorHandlerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private Cache cache;
private CacheErrorHandler errorHandler;
private SimpleService simpleService;
@Before
public void setup() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
this.cache = context.getBean("mockCache", Cache.class);
this.errorHandler = context.getBean(CacheErrorHandler.class);
this.simpleService = context.getBean(SimpleService.class);
}
@Test
public void getFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
SimpleGeneratedCacheKey key = new SimpleGeneratedCacheKey(0L);
doThrow(exception).when(cache).get(key);
this.simpleService.get(0L);
verify(errorHandler).handleCacheGetError(exception, cache, key);
}
@Test
public void putFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on put");
SimpleGeneratedCacheKey key = new SimpleGeneratedCacheKey(0L);
doThrow(exception).when(cache).put(key, 234L);
this.simpleService.put(0L, 234L);
verify(errorHandler).handleCachePutError(exception, cache, key, 234L);
}
@Test
public void evictFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
SimpleGeneratedCacheKey key = new SimpleGeneratedCacheKey(0L);
doThrow(exception).when(cache).evict(key);
this.simpleService.evict(0L);
verify(errorHandler).handleCacheEvictError(exception, cache, key);
}
@Test
public void clearFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
doThrow(exception).when(cache).clear();
this.simpleService.clear();
verify(errorHandler).handleCacheClearError(exception, cache);
}
@Configuration
@EnableCaching
static class Config {
@Bean(name = "jCacheInterceptor")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JCacheInterceptor cacheInterceptor() {
JCacheInterceptor interceptor = new JCacheInterceptor();
interceptor.setCacheOperationSource(cacheOperationSource());
interceptor.setErrorHandler(errorHandler());
return interceptor;
}
@Bean
public CacheErrorHandler errorHandler() {
return mock(CacheErrorHandler.class);
}
@Bean(name = "jCacheOperationSource")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public JCacheOperationSource cacheOperationSource() {
DefaultJCacheOperationSource source = new DefaultJCacheOperationSource();
source.setCacheManager(cacheManager());
return source;
}
@Bean
public SimpleService simpleService() {
return new SimpleService();
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(mockCache()));
return cacheManager;
}
@Bean
public Cache mockCache() {
Cache cache = mock(Cache.class);
given(cache.getName()).willReturn("test");
return cache;
}
}
@CacheDefaults(cacheName = "test")
public static class SimpleService {
private AtomicLong counter = new AtomicLong();
@CacheResult
public Object get(long id) {
return counter.getAndIncrement();
}
@CachePut
public void put(long id, @CacheValue Object object) {
}
@CacheRemove
public void evict(long id) {
}
@CacheRemoveAll
public void clear() {
}
}
}

View File

@ -7,7 +7,7 @@
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven proxy-target-class="false" order="0"/>
<cache:annotation-driven proxy-target-class="false" order="0" error-handler="errorHandler"/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
@ -26,6 +26,8 @@
</property>
</bean>
<bean id="errorHandler" class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/>
<bean id="defaultCache"
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>

View File

@ -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.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Configuration;
@ -51,6 +52,8 @@ public abstract class AbstractCachingConfiguration<C extends CachingConfigurer>
protected KeyGenerator keyGenerator;
protected CacheErrorHandler errorHandler;
@Autowired(required=false)
private Collection<CacheManager> cacheManagerBeans;
@ -115,6 +118,7 @@ public abstract class AbstractCachingConfiguration<C extends CachingConfigurer>
this.cacheManager = config.cacheManager();
this.cacheResolver = config.cacheResolver();
this.keyGenerator = config.keyGenerator();
this.errorHandler = config.errorHandler();
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.cache.annotation;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
@ -104,4 +105,26 @@ public interface CachingConfigurer {
*/
KeyGenerator keyGenerator();
/**
* Return the {@link CacheErrorHandler} to use to handle cache-related errors.
* <p>By default,{@link org.springframework.cache.interceptor.SimpleCacheErrorHandler}
* is used and simply throws the exception back at the client.
* <p>Implementations must explicitly declare
* {@link org.springframework.context.annotation.Bean @Bean}, e.g.
* <pre class="code">
* &#064;Configuration
* &#064;EnableCaching
* public class AppConfig extends CachingConfigurerSupport {
* &#064;Bean // important!
* &#064;Override
* public CacheErrorHandler errorHandler() {
* // configure and return CacheErrorHandler instance
* }
* // ...
* }
* </pre>
* See @{@link EnableCaching} for more complete examples.
*/
CacheErrorHandler errorHandler();
}

View File

@ -17,6 +17,7 @@
package org.springframework.cache.annotation;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
@ -45,4 +46,8 @@ public class CachingConfigurerSupport implements CachingConfigurer {
return null;
}
@Override
public CacheErrorHandler errorHandler() {
return null;
}
}

View File

@ -91,11 +91,11 @@ import org.springframework.core.Ordered;
* <p>For those that wish to establish a more direct relationship between
* {@code @EnableCaching} and the exact cache manager bean to be used,
* the {@link CachingConfigurer} callback interface may be implemented - notice the
* {@code implements} clause and the {@code @Override}-annotated methods below:
* the {@code @Override}-annotated methods below:
* <pre class="code">
* &#064;Configuration
* &#064;EnableCaching
* public class AppConfig implements CachingConfigurer {
* public class AppConfig extends CachingConfigurerSupport {
* &#064;Bean
* public MyService myService() {
* // configure and return a class having &#064;Cacheable methods
@ -128,9 +128,14 @@ import org.springframework.core.Ordered;
* {@code @EnableCaching} will configure Spring's
* {@link org.springframework.cache.interceptor.SimpleKeyGenerator SimpleKeyGenerator}
* for this purpose, but when implementing {@code CachingConfigurer}, a key generator
* must be provided explicitly. Return {@code new SimpleKeyGenerator()} from this method
* if no customization is necessary. See {@link CachingConfigurer} Javadoc for further
* details.
* must be provided explicitly. Return {@code null} or {@code new SimpleKeyGenerator()}
* from this method if no customization is necessary.
*
* <p>{@link CachingConfigurer} offers additional customization options: it is recommended
* to extend from {@link org.springframework.cache.annotation.CachingConfigurerSupport
* CachingConfigurerSupport} that provides a default implementation for all methods which
* can be useful if you do not need to customize everything. See {@link CachingConfigurer}
* Javadoc for further details.
*
* <p>The {@link #mode()} attribute controls how advice is applied; if the mode is
* {@link AdviceMode#PROXY} (the default), then the other attributes such as

View File

@ -68,6 +68,9 @@ public class ProxyCachingConfiguration extends AbstractCachingConfiguration<Cach
if (this.keyGenerator != null) {
interceptor.setKeyGenerator(this.keyGenerator);
}
if (this.errorHandler != null) {
interceptor.setErrorHandler(this.errorHandler);
}
return interceptor;
}

View File

@ -32,6 +32,7 @@ import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
@ -102,6 +103,13 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
new RuntimeBeanReference(CacheNamespaceHandler.extractCacheManager(element)));
}
private static BeanDefinition parseErrorHandler(Element element, BeanDefinition def) {
String name = element.getAttribute("error-handler");
if (StringUtils.hasText(name)) {
def.getPropertyValues().add("errorHandler", new RuntimeBeanReference(name.trim()));
}
return def;
}
/**
* Configure the necessary infrastructure to support the Spring's caching annotations.
@ -123,6 +131,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parseCacheManagerProperty(element, interceptorDef);
parseErrorHandler(element, interceptorDef);
CacheNamespaceHandler.parseKeyGenerator(element, interceptorDef);
interceptorDef.getPropertyValues().add("cacheOperationSources", new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
@ -186,6 +195,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
interceptorDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
parseErrorHandler(element, interceptorDef);
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// Create the CacheAdvisor definition.

View File

@ -0,0 +1,115 @@
/*
* 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 org.springframework.cache.Cache;
import org.springframework.util.Assert;
/**
* A base component for invoking {@link Cache} operations and using a
* configurable {@link CacheErrorHandler} when an exception occurs.
*
* @author Stephane Nicoll
* @since 4.1
* @see org.springframework.cache.interceptor.CacheErrorHandler
*/
public abstract class AbstractCacheInvoker {
private CacheErrorHandler errorHandler;
protected AbstractCacheInvoker(CacheErrorHandler errorHandler) {
Assert.notNull("ErrorHandler must not be null");
this.errorHandler = errorHandler;
}
protected AbstractCacheInvoker() {
this(new SimpleCacheErrorHandler());
}
/**
* Set the {@link CacheErrorHandler} instance to use to handle errors
* thrown by the cache provider. By default, a {@link SimpleCacheErrorHandler}
* is used who throws any exception as is.
*/
public void setErrorHandler(CacheErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Return the {@link CacheErrorHandler} to use.
*/
public CacheErrorHandler getErrorHandler() {
return this.errorHandler;
}
/**
* Execute {@link Cache#get(Object)} on the specified {@link Cache} and
* invoke the error handler if an exception occurs. Return {@code null}
* if the handler does not throw any exception, which simulates a cache
* miss in case of error.
* @see Cache#get(Object)
*/
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
try {
return cache.get(key);
}
catch (RuntimeException e) {
getErrorHandler().handleCacheGetError(e, cache, key);
return null; // If the exception is handled, return a cache miss
}
}
/**
* Execute {@link Cache#put(Object, Object)} on the specified {@link Cache}
* and invoke the error handler if an exception occurs.
*/
protected void doPut(Cache cache, Object key, Object result) {
try {
cache.put(key, result);
}
catch (RuntimeException e) {
getErrorHandler().handleCachePutError(e, cache, key, result);
}
}
/**
* Execute {@link Cache#evict(Object)} on the specified {@link Cache} and
* invoke the error handler if an exception occurs.
*/
protected void doEvict(Cache cache, Object key) {
try {
cache.evict(key);
}
catch (RuntimeException e) {
getErrorHandler().handleCacheEvictError(e, cache, key);
}
}
/**
* Execute {@link Cache#clear()} on the specified {@link Cache} and
* invoke the error handler if an exception occurs.
*/
protected void doClear(Cache cache) {
try {
cache.clear();
}
catch (RuntimeException e) {
getErrorHandler().handleCacheClearError(e, cache);
}
}
}

View File

@ -69,7 +69,8 @@ import org.springframework.util.StringUtils;
* @author Stephane Nicoll
* @since 3.1
*/
public abstract class CacheAspectSupport implements InitializingBean, ApplicationContextAware {
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements InitializingBean, ApplicationContextAware {
protected final Log logger = LogFactory.getLog(getClass());
@ -168,6 +169,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
"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.getErrorHandler() != null, "The 'errorHandler' is required.");
Assert.state(this.applicationContext != null, "The application context was not injected as it should.");
this.initialized = true;
}
@ -343,14 +345,14 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
for (Cache cache : context.getCaches()) {
if (operation.isCacheWide()) {
logInvalidating(context, operation, null);
cache.clear();
doClear(cache);
}
else {
if (key == null) {
key = context.generateKey(result);
}
logInvalidating(context, operation, key);
cache.evict(key);
doEvict(cache, key);
}
}
}
@ -402,7 +404,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
for (Cache cache : context.getCaches()) {
Cache.ValueWrapper wrapper = cache.get(key);
Cache.ValueWrapper wrapper = doGet(cache, key);
if (wrapper != null) {
return wrapper;
}
@ -571,7 +573,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
}
private static class CachePutRequest {
private class CachePutRequest {
private final CacheOperationContext context;
@ -585,7 +587,7 @@ public abstract class CacheAspectSupport implements InitializingBean, Applicatio
public void apply(Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
cache.put(this.key, result);
doPut(cache, this.key, result);
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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 org.springframework.cache.Cache;
/**
* A strategy for handling cache-related errors. In most cases, any
* exception thrown by the provider should simply be thrown back at
* the client but, in some circumstances, the infrastructure may need
* to handle cache-provider exceptions in a different way.
*
* <p>Typically, failing to retrieve an object from the cache with
* a given id can be transparently managed as a cache miss by not
* throwing back such exception.
*
* @author Stephane Nicoll
* @since 4.1
*/
public interface CacheErrorHandler {
/**
* Handle the given runtime exception thrown by the cache provider when
* retrieving an item with the specified {@code key}, possibly
* rethrowing it as a fatal exception.
* @param exception the exception thrown by the cache provider
* @param cache the cache
* @param key the key used to get the item
* @see Cache#get(Object)
*/
void handleCacheGetError(RuntimeException exception, Cache cache, Object key);
/**
* Handle the given runtime exception thrown by the cache provider when
* updating an item with the specified {@code key} and {@code value},
* possibly rethrowing it as a fatal exception.
* @param exception the exception thrown by the cache provider
* @param cache the cache
* @param key the key used to update the item
* @param value the value to associate with the key
* @see Cache#put(Object, Object)
*/
void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value);
/**
* Handle the given runtime exception thrown by the cache provider when
* clearing an item with the specified {@code key}, possibly rethrowing
* it as a fatal exception.
* @param exception the exception thrown by the cache provider
* @param cache the cache
* @param key the key used to clear the item
*/
void handleCacheEvictError(RuntimeException exception, Cache cache, Object key);
/**
* Handle the given runtime exception thrown by the cache provider when
* clearing the specified {@link Cache}, possibly rethrowing it as a
* fatal exception.
* @param exception the exception thrown by the cache provider
* @param cache the cache to clear
*/
void handleCacheClearError(RuntimeException exception, Cache cache);
}

View File

@ -0,0 +1,49 @@
/*
* 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 org.springframework.cache.Cache;
/**
* A simple {@link CacheErrorHandler} that does not handle the
* exception at all, simply throwing it back at the client.
*
* @author Stephane Nicoll
* @since 4.1
*/
public class SimpleCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
throw exception;
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
throw exception;
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
throw exception;
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
throw exception;
}
}

View File

@ -27,4 +27,5 @@ http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/
http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.0.xsd=org/springframework/cache/config/spring-cache-4.0.xsd
http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-4.0.xsd
http\://www.springframework.org/schema/cache/spring-cache-4.1.xsd=org/springframework/cache/config/spring-cache-4.1.xsd
http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-4.1.xsd

View File

@ -0,0 +1,288 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/cache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/cache"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-4.1.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-4.1.xsd"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the elements used in the Spring Framework's declarative
cache management infrastructure.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="annotation-driven">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.annotation.AnnotationCacheOperationDefinitionSource"><![CDATA[
Indicates that cache configuration is defined by Java 5
annotations on bean classes, and that proxies are automatically
to be created for the relevant annotated beans.
The default annotations supported are Spring's @Cacheable and @CacheEvict.
See org.springframework.cache.annotation.EnableCaching Javadoc
for information on code-based alternatives to this XML element.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
The bean name of the CacheManager that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the bean name of the desired CacheManager
is not 'cacheManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.CacheManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.interceptor.CacheErrorHandler"><![CDATA[
The bean name of the CacheErrorHandler that is to be used to handle cache-related errors.
This attribute is not required, and only needs to be specified
explicitly if the default strategy (SimpleCacheErrorHandler) is not sufficient.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.interceptor.CacheErrorHandler"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mode" default="proxy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Should annotated beans be proxied using Spring's AOP framework,
or should they rather be weaved with an AspectJ transaction aspect?
AspectJ weaving requires spring-aspects.jar on the classpath,
as well as load-time weaving (or compile-time weaving) enabled.
Note: The weaving-based aspect requires the @Cacheable and @CacheInvalidate
annotations to be defined on the concrete class. Annotations in interfaces
will not work in that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="proxy"/>
<xsd:enumeration value="aspectj"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Are class-based (CGLIB) proxies to be created? By default, standard
Java interface-based proxies are created.
Note: Class-based proxies require the @Cacheable and @CacheInvalidate annotations
to be defined on the concrete class. Annotations in interfaces will not work
in that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:token">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
Controls the ordering of the execution of the cache advisor
when multiple advice executes at a specific joinpoint.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="advice">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.interceptor.CacheInterceptor"><![CDATA[
Defines the cache semantics of the AOP advice that is to be
executed.
That is, this advice element is where the cacheable semantics of
any number of methods are defined (where cacheable semantics
includes the backing cache(s), the key, cache condition rules, and suchlike).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="java:org.springframework.cache.interceptor.CacheInterceptor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:sequence>
<xsd:element name="caching" type="definitionsType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
The bean name of the CacheManager that is to be used
for storing and retrieving data.
This attribute is not required, and only needs to be specified
explicitly if the bean name of the desired CacheManager
is not 'cacheManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.CacheManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="basedefinitionType">
<xsd:attribute name="cache" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the backing cache(s). Multiple caches can be specified by separating them using comma: 'orders, books']]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression used for computing the cache key, mutually exclusive with the key-generator parameter.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key-generator" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the KeyGenerator bean responsible to compute the key, mutually exclusive with the key parameter.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="cache-manager" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the CacheManager bean responsible to manage the operation.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="condition" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression used for conditioning the method caching.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The method name(s) with which the cache attributes are to be
associated. The wildcard (*) character can be used to associate the
same cache attribute settings with a number of methods; for
example, 'get*', 'handle*', '*Order', 'on*Event', etc.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="definitionsType">
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="cacheable" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:attribute name="unless" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression used to veto the method caching.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="cache-put" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:attribute name="unless" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The SpEL expression used to veto the method caching.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="cache-evict" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:attribute name="all-entries" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether all the entries should be evicted.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="before-invocation" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether the eviction should occur after the method is successfully
invoked (default) or before.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@ -16,9 +16,11 @@
package org.springframework.cache.config;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
@ -36,9 +38,16 @@ public class AnnotationNamespaceDrivenTests extends AbstractAnnotationTests {
}
@Test
public void testKeyStrategy() throws Exception {
public void testKeyStrategy() {
CacheInterceptor ci = ctx.getBean("org.springframework.cache.interceptor.CacheInterceptor#0",
CacheInterceptor.class);
assertSame(ctx.getBean("keyGenerator"), ci.getKeyGenerator());
}
@Test
public void testCacheErrorHandler() {
CacheInterceptor ci = ctx.getBean("org.springframework.cache.interceptor.CacheInterceptor#0",
CacheInterceptor.class);
assertSame(ctx.getBean("errorHandler", CacheErrorHandler.class), ci.getErrorHandler());
}
}

View File

@ -24,10 +24,12 @@ import org.springframework.cache.CacheManager;
import org.springframework.cache.CacheTestUtils;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
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.SimpleCacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.context.ConfigurableApplicationContext;
@ -50,11 +52,17 @@ public class EnableCachingTests extends AbstractAnnotationTests {
}
@Test
public void testKeyStrategy() throws Exception {
public void testKeyStrategy() {
CacheInterceptor ci = ctx.getBean(CacheInterceptor.class);
assertSame(ctx.getBean("keyGenerator", KeyGenerator.class), ci.getKeyGenerator());
}
@Test
public void testCacheErrorHandler() {
CacheInterceptor ci = ctx.getBean(CacheInterceptor.class);
assertSame(ctx.getBean("errorHandler", CacheErrorHandler.class), ci.getErrorHandler());
}
// --- local tests -------
@Test
@ -160,6 +168,12 @@ public class EnableCachingTests extends AbstractAnnotationTests {
return new SomeKeyGenerator();
}
@Override
@Bean
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler();
}
@Bean
public KeyGenerator customKeyGenerator() {
return new SomeCustomKeyGenerator();

View File

@ -0,0 +1,241 @@
/*
* 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.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.doReturn;
import static org.mockito.BDDMockito.doThrow;
import static org.mockito.BDDMockito.*;
import static org.mockito.BDDMockito.verify;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* @author Stephane Nicoll
*/
public class CacheErrorHandlerTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private Cache cache;
private CacheInterceptor cacheInterceptor;
private CacheErrorHandler errorHandler;
private SimpleService simpleService;
@Before
public void setup() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
this.cache = context.getBean("mockCache", Cache.class);
this.cacheInterceptor = context.getBean(CacheInterceptor.class);
this.errorHandler = context.getBean(CacheErrorHandler.class);
this.simpleService = context.getBean(SimpleService.class);
}
@Test
public void getFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
doThrow(exception).when(cache).get(0L);
Object result = this.simpleService.get(0L);
verify(errorHandler).handleCacheGetError(exception, cache, 0L);
verify(cache).get(0L);
verify(cache).put(0L, result); // result of the invocation
}
@Test
public void getAndPutFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
doThrow(exception).when(cache).get(0L);
doThrow(exception).when(cache).put(0L, 0L); // Update of the cache will fail as well
Object counter = this.simpleService.get(0L);
doReturn(new SimpleValueWrapper(2L)).when(cache).get(0L);
Object counter2 = this.simpleService.get(0L);
Object counter3 = this.simpleService.get(0L);
assertNotSame(counter, counter2);
assertEquals(counter2, counter3);
}
@Test
public void getFailProperException() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on get");
doThrow(exception).when(cache).get(0L);
cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
thrown.expect(is(exception));
this.simpleService.get(0L);
}
@Test
public void putFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on put");
doThrow(exception).when(cache).put(0L, 0L);
this.simpleService.put(0L);
verify(errorHandler).handleCachePutError(exception, cache, 0L, 0L);
}
@Test
public void putFailProperException() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on put");
doThrow(exception).when(cache).put(0L, 0L);
cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
thrown.expect(is(exception));
this.simpleService.put(0L);
}
@Test
public void evictFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
doThrow(exception).when(cache).evict(0L);
this.simpleService.evict(0L);
verify(errorHandler).handleCacheEvictError(exception, cache, 0L);
}
@Test
public void evictFailProperException() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
doThrow(exception).when(cache).evict(0L);
cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
thrown.expect(is(exception));
this.simpleService.evict(0L);
}
@Test
public void clearFail() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
doThrow(exception).when(cache).clear();
this.simpleService.clear();
verify(errorHandler).handleCacheClearError(exception, cache);
}
@Test
public void clearFailProperException() {
UnsupportedOperationException exception = new UnsupportedOperationException("Test exception on evict");
doThrow(exception).when(cache).clear();
cacheInterceptor.setErrorHandler(new SimpleCacheErrorHandler());
thrown.expect(is(exception));
this.simpleService.clear();
}
@Configuration
@EnableCaching
static class Config {
@Bean
public CacheInterceptor cacheInterceptor() {
CacheInterceptor cacheInterceptor = new CacheInterceptor();
cacheInterceptor.setCacheManager(cacheManager());
cacheInterceptor.setCacheOperationSources(cacheOperationSource());
cacheInterceptor.setErrorHandler(errorHandler());
return cacheInterceptor;
}
@Bean
public CacheErrorHandler errorHandler() {
return mock(CacheErrorHandler.class);
}
@Bean
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
public SimpleService simpleService() {
return new SimpleService();
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(mockCache()));
return cacheManager;
}
@Bean
public Cache mockCache() {
Cache cache = mock(Cache.class);
given(cache.getName()).willReturn("test");
return cache;
}
}
@CacheConfig(cacheNames = "test")
public static class SimpleService {
private AtomicLong counter = new AtomicLong();
@Cacheable
public Object get(long id) {
return counter.getAndIncrement();
}
@CachePut
public Object put(long id) {
return counter.getAndIncrement();
}
@CacheEvict
public void evict(long id) {
}
@CacheEvict(allEntries = true)
public void clear() {
}
}
}

View File

@ -7,7 +7,8 @@
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven proxy-target-class="false" order="0" key-generator="keyGenerator"/>
<cache:annotation-driven proxy-target-class="false" order="0"
key-generator="keyGenerator" error-handler="errorHandler"/>
<aop:config>
<aop:advisor advice-ref="debugInterceptor" pointcut="execution(* *..CacheableService.*(..))" order="1"/>
@ -31,6 +32,8 @@
<bean id="keyGenerator" class="org.springframework.cache.config.SomeKeyGenerator"/>
<bean id="errorHandler" class="org.springframework.cache.interceptor.SimpleCacheErrorHandler"/>
<bean id="customKeyGenerator" class="org.springframework.cache.config.SomeCustomKeyGenerator"/>
<bean id="customCacheManager" class="org.springframework.cache.support.SimpleCacheManager">

View File

@ -47196,6 +47196,17 @@ application through AOP. The configuration is intentionally similar with that of
| Name of cache manager to use. Only required if the name of the cache manager is not
`cacheManager`.
| `key-generator`
| N/A (See `CachingConfigurer` javadocs)
| `SimpleKeyGenerator`
| Name of the custom key generator to use.
| `error-handler`
| N/A (See `CachingConfigurer` javadocs)
| `SimpleCacheErrorHandler`
| Name of the custom cache error handler to use. By default, any exception throw during
a cache related operations are thrown back at the client.
| `mode`
| `mode`
| proxy