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

@@ -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;
}
}