+ align @CacheEvict behaviour with @Cacheable and @CachePut

+ add flag for post method execution
+ add integration tests
This commit is contained in:
Costin Leau
2011-11-28 12:06:34 +00:00
parent b2bc7534c2
commit f91f778fb5
11 changed files with 261 additions and 53 deletions

View File

@@ -63,4 +63,10 @@ public @interface CacheEvict {
*/
boolean allEntries() default false;
/**
* Whether the eviction should occur after the method is successfully invoked (default)
* or before. The latter causes the eviction to occur irrespective of the method outcome (whether
* it threw an exception or not) while the former does not.
*/
boolean afterInvocation() default true;
}

View File

@@ -85,6 +85,7 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
ceo.setCondition(caching.condition());
ceo.setKey(caching.key());
ceo.setCacheWide(caching.allEntries());
ceo.setAfterInvocation(caching.afterInvocation());
ceo.setName(ae.toString());
return ceo;
}

View File

@@ -68,7 +68,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
}
}
CacheOperation merge(Element element, ReaderContext readerCtx, CacheOperation op) {
<T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
String cache = element.getAttribute("cache");
String k = element.getAttribute("key");
String c = element.getAttribute("condition");
@@ -181,7 +181,17 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
CacheEvictOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
String wide = opElement.getAttribute("all-entries");
if (StringUtils.hasText(wide)) {
op.setCacheWide(Boolean.valueOf(wide.trim()));
}
String after = opElement.getAttribute("after-invocation");
if (StringUtils.hasText(after)) {
op.setAfterInvocation(Boolean.valueOf(after.trim()));
}
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {

View File

@@ -189,11 +189,10 @@ public abstract class CacheAspectSupport implements InitializingBean {
// analyze caching information
if (!CollectionUtils.isEmpty(cacheOp)) {
Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target,
targetClass);
Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target, targetClass);
// start with evictions
inspectCacheEvicts(ops.get(EVICT));
inspectBeforeCacheEvicts(ops.get(EVICT));
// follow up with cacheable
CacheStatus status = inspectCacheables(ops.get(CACHEABLE));
@@ -213,6 +212,8 @@ public abstract class CacheAspectSupport implements InitializingBean {
retVal = invoker.invoke();
inspectAfterCacheEvicts(ops.get(EVICT));
if (!updates.isEmpty()) {
update(updates, retVal);
}
@@ -223,42 +224,51 @@ public abstract class CacheAspectSupport implements InitializingBean {
return invoker.invoke();
}
private void inspectCacheEvicts(Collection<CacheOperationContext> evictions) {
private void inspectBeforeCacheEvicts(Collection<CacheOperationContext> evictions) {
inspectAfterCacheEvicts(evictions, false);
}
private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions) {
inspectAfterCacheEvicts(evictions, true);
}
private void inspectAfterCacheEvicts(Collection<CacheOperationContext> evictions, boolean afterInvocation) {
if (!evictions.isEmpty()) {
boolean log = logger.isTraceEnabled();
for (CacheOperationContext context : evictions) {
if (context.isConditionPassing()) {
CacheEvictOperation evictOp = (CacheEvictOperation) context.operation;
CacheEvictOperation evictOp = (CacheEvictOperation) context.operation;
// for each cache
// lazy key initialization
Object key = null;
if (afterInvocation == evictOp.isAfterInvocation()) {
if (context.isConditionPassing()) {
// for each cache
// lazy key initialization
Object key = null;
for (Cache cache : context.getCaches()) {
// cache-wide flush
if (evictOp.isCacheWide()) {
cache.clear();
if (log) {
logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method);
for (Cache cache : context.getCaches()) {
// cache-wide flush
if (evictOp.isCacheWide()) {
cache.clear();
if (log) {
logger.trace("Invalidating entire cache for operation " + evictOp + " on method " + context.method);
}
} else {
// check key
if (key == null) {
key = context.generateKey();
}
if (log) {
logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method);
}
cache.evict(key);
}
} else {
// check key
if (key == null) {
key = context.generateKey();
}
if (log) {
logger.trace("Invalidating cache key " + key + " for operation " + evictOp + " on method " + context.method);
}
cache.evict(key);
}
}
}
else {
if (log) {
logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
} else {
if (log) {
logger.trace("Cache condition failed on method " + context.method + " for operation " + context.operation);
}
}
}
}

View File

@@ -25,6 +25,7 @@ package org.springframework.cache.interceptor;
public class CacheEvictOperation extends CacheOperation {
private boolean cacheWide = false;
private boolean afterInvocation = true;
public void setCacheWide(boolean cacheWide) {
this.cacheWide = cacheWide;
@@ -34,11 +35,21 @@ public class CacheEvictOperation extends CacheOperation {
return this.cacheWide;
}
public void setAfterInvocation(boolean afterInvocation) {
this.afterInvocation = afterInvocation;
}
public boolean isAfterInvocation() {
return this.afterInvocation;
}
@Override
protected StringBuilder getOperationDescription() {
StringBuilder sb = super.getOperationDescription();
sb.append(",");
sb.append(this.cacheWide);
sb.append(",");
sb.append(this.afterInvocation);
return sb;
}
}