+ add support for multiple cache names
+ require each annotation to specify a cache name
+ add method support in Key generator interface
+ add bug fix for embedded JDK concurrent declaration
This commit is contained in:
Costin Leau 2010-12-16 13:19:01 +00:00
parent 21d64a74ae
commit faf01b0337
17 changed files with 159 additions and 94 deletions

View File

@ -1,4 +1,4 @@
#Thu Dec 18 06:34:28 PST 2008
eclipse.preferences.version=1
formatter_profile=org.eclipse.jdt.ui.default.eclipse_profile
formatter_settings_version=11
#Wed Mar 31 18:40:01 EEST 2010
eclipse.preferences.version=1
formatter_profile=_Spring
formatter_settings_version=11

View File

@ -16,14 +16,15 @@
package org.springframework.cache;
import java.lang.reflect.Method;
/**
* Cache 'key' extractor. Used for creating a key based on the given
* parameters.
* Cache 'key' extractor. Used for creating a key based on the given method
* (used as context) and its parameter.
*
* @author Costin Leau
*/
// CL: could be renamed to KeyFactory
public interface KeyGenerator<K> {
K extract(Object... params);
K extract(Method method, Object... params);
}

View File

@ -107,7 +107,7 @@ public class AnnotationCacheDefinitionSource extends AbstractFallbackCacheDefini
*/
protected CacheDefinition determineCacheDefinition(AnnotatedElement ae) {
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
CacheDefinition attr = annotationParser.parseTransactionAnnotation(ae);
CacheDefinition attr = annotationParser.parseCacheAnnotation(ae);
if (attr != null) {
return attr;
}

View File

@ -42,5 +42,5 @@ public interface CacheAnnotationParser {
* or <code>null</code> if none was found
* @see AnnotationCacheDefinitionSource#determineCacheOperationDefinition
*/
CacheDefinition parseTransactionAnnotation(AnnotatedElement ae);
CacheDefinition parseCacheAnnotation(AnnotatedElement ae);
}

View File

@ -40,7 +40,7 @@ public @interface CacheEvict {
* <p>May be used to determine the target cache (or caches), matching the qualifier
* value (or the bean name(s)) of (a) specific bean definition.
*/
String value() default "";
String[] value();
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
@ -57,11 +57,11 @@ public @interface CacheEvict {
String condition() default "";
/**
* Whether or not all the entries inside the cache are removed or not.
* By default, only the value under the associated key is removed.
* Whether or not all the entries inside the cache(s) are removed or not. By
* default, only the value under the associated key is removed.
*
* Note that specifying setting this parameter to true and specifying a
* {@link CacheKey key} is not allowed.
* {@link CacheKey key} is not allowed.
*/
boolean allEntries() default false;
}

View File

@ -37,11 +37,12 @@ import java.lang.annotation.Target;
public @interface Cacheable {
/**
* Name of the cache in which the update takes place.
* <p>May be used to determine the target cache (or caches), matching the qualifier
* value (or the bean name(s)) of (a) specific bean definition.
* Name of the caches in which the update takes place.
* <p>
* May be used to determine the target cache (or caches), matching the
* qualifier value (or the bean name(s)) of (a) specific bean definition.
*/
String value() default "";
String[] value();
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.

View File

@ -20,8 +20,8 @@ import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import org.springframework.cache.interceptor.CacheInvalidateDefinition;
import org.springframework.cache.interceptor.CacheDefinition;
import org.springframework.cache.interceptor.CacheInvalidateDefinition;
import org.springframework.cache.interceptor.CacheUpdateDefinition;
import org.springframework.cache.interceptor.DefaultCacheInvalidateDefinition;
import org.springframework.cache.interceptor.DefaultCacheUpdateDefinition;
@ -34,17 +34,17 @@ import org.springframework.cache.interceptor.DefaultCacheUpdateDefinition;
@SuppressWarnings("serial")
public class SpringCachingAnnotationParser implements CacheAnnotationParser, Serializable {
public CacheDefinition parseTransactionAnnotation(AnnotatedElement ae) {
public CacheDefinition parseCacheAnnotation(AnnotatedElement ae) {
Cacheable update = findAnnotation(ae, Cacheable.class);
if (update != null) {
return parseCacheableAnnotation(update);
return parseCacheableAnnotation(ae, update);
}
CacheEvict invalidate = findAnnotation(ae, CacheEvict.class);
if (invalidate != null) {
return parseInvalidateAnnotation(invalidate);
return parseInvalidateAnnotation(ae, invalidate);
}
return null;
@ -63,22 +63,24 @@ public class SpringCachingAnnotationParser implements CacheAnnotationParser, Ser
return ann;
}
public CacheUpdateDefinition parseCacheableAnnotation(Cacheable ann) {
CacheUpdateDefinition parseCacheableAnnotation(AnnotatedElement target, Cacheable ann) {
DefaultCacheUpdateDefinition dcud = new DefaultCacheUpdateDefinition();
dcud.setCacheName(ann.value());
dcud.setCacheNames(ann.value());
dcud.setCondition(ann.condition());
dcud.setKey(ann.key());
dcud.setName(target.toString());
return dcud;
}
public CacheInvalidateDefinition parseInvalidateAnnotation(CacheEvict ann) {
CacheInvalidateDefinition parseInvalidateAnnotation(AnnotatedElement target, CacheEvict ann) {
DefaultCacheInvalidateDefinition dcid = new DefaultCacheInvalidateDefinition();
dcid.setCacheName(ann.value());
dcid.setCacheNames(ann.value());
dcid.setCondition(ann.condition());
dcid.setKey(ann.key());
dcid.setCacheWide(ann.allEntries());
dcid.setName(target.toString());
return dcid;
}
}

View File

@ -21,6 +21,7 @@ import java.util.concurrent.ConcurrentMap;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
/**
* Factory bean for easy configuration of {@link ConcurrentCache} through Spring.
@ -52,7 +53,9 @@ public class ConcurrentCacheFactoryBean<K, V> implements FactoryBean<ConcurrentC
}
public void setBeanName(String beanName) {
setName(beanName);
if (!StringUtils.hasText(name)) {
setName(beanName);
}
}
public void setName(String name) {

View File

@ -16,6 +16,11 @@
package org.springframework.cache.interceptor;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.util.Assert;
/**
* Base class implementing {@link CacheDefinition}.
@ -24,14 +29,14 @@ package org.springframework.cache.interceptor;
*/
abstract class AbstractCacheDefinition implements CacheDefinition {
private String cacheName = "";
private Set<String> cacheNames = Collections.emptySet();
private String condition = "";
private String key = "";
private String name = "";
public String getCacheName() {
return cacheName;
public Set<String> getCacheNames() {
return cacheNames;
}
public String getCondition() {
@ -47,18 +52,30 @@ abstract class AbstractCacheDefinition implements CacheDefinition {
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
Assert.hasText(cacheName);
this.cacheNames = Collections.singleton(cacheName);
}
public void setCacheNames(String[] cacheNames) {
Assert.notEmpty(cacheNames);
this.cacheNames = new LinkedHashSet<String>(cacheNames.length);
for (String string : cacheNames) {
this.cacheNames.add(string);
}
}
public void setCondition(String condition) {
Assert.notNull(condition);
this.condition = condition;
}
public void setKey(String key) {
Assert.notNull(key);
this.key = key;
}
public void setName(String name) {
Assert.hasText(name);
this.name = name;
}
@ -97,12 +114,15 @@ abstract class AbstractCacheDefinition implements CacheDefinition {
*/
protected StringBuilder getDefinitionDescription() {
StringBuilder result = new StringBuilder();
result.append(cacheName);
result.append(',');
result.append("CacheDefinition[");
result.append(name);
result.append("] caches=");
result.append(cacheNames);
result.append(" | condition='");
result.append(condition);
result.append(",");
result.append("' | key='");
result.append(key);
result.append("'");
return result;
}
}

View File

@ -18,6 +18,9 @@ package org.springframework.cache.interceptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
@ -58,8 +61,11 @@ import org.springframework.util.StringUtils;
public abstract class CacheAspectSupport implements InitializingBean {
private static class EmptyHolder implements Serializable {
private static final long serialVersionUID = 1L;
}
// TODO: can null values be properly stored into user caches?
private static final Object NULL_RETURN = new EmptyHolder();
protected final Log logger = LogFactory.getLog(getClass());
@ -116,21 +122,30 @@ public abstract class CacheAspectSupport implements InitializingBean {
cacheDefinitionSources) : cacheDefinitionSources[0]);
}
protected <K, V> Cache<K, V> getCache(CacheDefinition definition) {
// TODO: add support for multiple caches
protected Collection<Cache<?, ?>> getCaches(CacheDefinition definition) {
// TODO: add behaviour for the default cache
String name = definition.getCacheName();
if (!StringUtils.hasText(name)) {
name = cacheManager.getCacheNames().iterator().next();
Set<String> cacheNames = definition.getCacheNames();
Collection<Cache<?,?>> caches = new ArrayList<Cache<?,?>>(cacheNames.size());
for (String cacheName : cacheNames) {
Cache<Object, Object> cache = cacheManager.getCache(cacheName);
if (cache == null){
throw new IllegalArgumentException("Cannot find cache named ["+cacheName+"] for " + definition);
}
caches.add(cache);
}
return cacheManager.getCache(name);
return caches;
}
protected CacheOperationContext getOperationContext(CacheDefinition definition, Method method, Object[] args, Class<?> targetClass) {
protected CacheOperationContext getOperationContext(CacheDefinition definition, Method method, Object[] args,
Class<?> targetClass) {
return new CacheOperationContext(definition, method, args, targetClass);
}
protected Object execute(Callable<Object> invocation, Object target, Method method, Object[] args) throws Exception {
protected Object execute(Callable<Object> invocation, Object target,
Method method, Object[] args) throws Exception {
// get backing class
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
@ -138,40 +153,47 @@ public abstract class CacheAspectSupport implements InitializingBean {
targetClass = target.getClass();
}
final CacheDefinition cacheDef = getCacheDefinitionSource().getCacheDefinition(method,
targetClass);
final CacheDefinition cacheDef = getCacheDefinitionSource()
.getCacheDefinition(method, targetClass);
Object retVal = null;
// analyze caching information
if (cacheDef != null) {
CacheOperationContext context = getOperationContext(cacheDef, method, args, targetClass);
Cache<Object, Object> cache = (Cache<Object, Object>) context.getCache();
CacheOperationContext context = getOperationContext(cacheDef,
method, args, targetClass);
Collection<Cache<?,?>> caches = context.getCaches();
if (context.hasConditionPassed()) {
// check operation
if (cacheDef instanceof CacheUpdateDefinition) {
// for each cache
// check cache first
Object key = context.generateKey();
retVal = cache.get(key);
if (retVal == null) {
retVal = invocation.call();
cache.put(key, (retVal == null ? NULL_RETURN : retVal));
for (Cache cache : caches) {
Object key = context.generateKey();
retVal = cache.get(key);
if (retVal == null) {
retVal = invocation.call();
cache.put(key, (retVal == null ? NULL_RETURN : retVal));
}
}
}
if (cacheDef instanceof CacheInvalidateDefinition) {
CacheInvalidateDefinition invalidateDef = (CacheInvalidateDefinition) cacheDef;
retVal = invocation.call();
// flush the cache (ignore arguments)
if (invalidateDef.isCacheWide()) {
cache.clear();
}
else {
// check key
Object key = context.generateKey();
cache.remove(key);
// for each cache
for (Cache cache : caches) {
// flush the cache (ignore arguments)
if (invalidateDef.isCacheWide()) {
cache.clear();
} else {
// check key
Object key = context.generateKey();
cache.remove(key);
}
}
}
@ -185,23 +207,24 @@ public abstract class CacheAspectSupport implements InitializingBean {
protected class CacheOperationContext {
private CacheDefinition definition;
private final Cache<?, ?> cache;
private final Collection<Cache<?, ?>> caches;
private final Method method;
private final Object[] args;
// context passed around to avoid multiple creations
private final EvaluationContext evalContext;
private final KeyGenerator keyGenerator = new DefaultKeyGenerator();
private final KeyGenerator<Object> keyGenerator = new DefaultKeyGenerator();
public CacheOperationContext(CacheDefinition operationDefinition, Method method, Object[] args,
Class<?> targetClass) {
public CacheOperationContext(CacheDefinition operationDefinition,
Method method, Object[] args, Class<?> targetClass) {
this.definition = operationDefinition;
this.cache = CacheAspectSupport.this.getCache(definition);
this.caches = CacheAspectSupport.this.getCaches(definition);
this.method = method;
this.args = args;
this.evalContext = evaluator.createEvaluationContext(cache, method, args, targetClass);
this.evalContext = evaluator.createEvaluationContext(caches, method,
args, targetClass);
}
/**
@ -212,7 +235,8 @@ public abstract class CacheAspectSupport implements InitializingBean {
*/
protected boolean hasConditionPassed() {
if (StringUtils.hasText(definition.getCondition())) {
return evaluator.condition(definition.getCondition(), method, evalContext);
return evaluator.condition(definition.getCondition(), method,
evalContext);
}
return true;
}
@ -221,8 +245,10 @@ public abstract class CacheAspectSupport implements InitializingBean {
* Computes the key for the given caching definition.
*
* @param definition
* @param method method being invoked
* @param objects arguments passed during the method invocation
* @param method
* method being invoked
* @param objects
* arguments passed during the method invocation
* @return generated key (null if none can be generated)
*/
protected Object generateKey() {
@ -230,11 +256,11 @@ public abstract class CacheAspectSupport implements InitializingBean {
return evaluator.key(definition.getKey(), method, evalContext);
}
return keyGenerator.extract(args);
return keyGenerator.extract(method, args);
}
protected Cache<?, ?> getCache() {
return cache;
protected Collection<Cache<?, ?>> getCaches() {
return caches;
}
}
}

View File

@ -16,6 +16,8 @@
package org.springframework.cache.interceptor;
import java.util.Set;
/**
* Interface describing Spring-compliant caching operation.
*
@ -33,11 +35,11 @@ public interface CacheDefinition {
String getName();
/**
* Returns the name of the cache against which this operation is performed.
* Returns the names of the cache against which this operation is performed.
*
* @return name of the cache on which the operation is performed.
* @return names of the cache on which the operation is performed.
*/
String getCacheName();
Set<String> getCacheNames();
/**
* Returns the SpEL expression conditioning the operation.

View File

@ -16,6 +16,8 @@
package org.springframework.cache.interceptor;
import java.util.Collection;
import org.springframework.cache.Cache;
/**
@ -33,9 +35,9 @@ interface CacheExpressionRootObject {
String getMethodName();
/**
* Returns the cache against which the method is executed.
* Returns the caches against which the method is executed.
*
* @return current cache
*/
Cache getCache();
Collection<Cache<?,?>> getCaches();
}

View File

@ -16,6 +16,8 @@
package org.springframework.cache.interceptor;
import java.util.Collection;
import org.springframework.cache.Cache;
import org.springframework.util.Assert;
@ -27,19 +29,19 @@ import org.springframework.util.Assert;
public class DefaultCacheExpressionRootObject implements CacheExpressionRootObject {
private final String methodName;
private final Cache cache;
private final Collection<Cache<?, ?>> caches;
public DefaultCacheExpressionRootObject(Cache cache, String methodName) {
public DefaultCacheExpressionRootObject(Collection<Cache<?,?>> caches, String methodName) {
Assert.hasText(methodName, "method name is required");
this.methodName = methodName;
this.cache = cache;
this.caches = caches;
}
public String getMethodName() {
return methodName;
}
public Cache getCache() {
return cache;
public Collection<Cache<?, ?>> getCaches() {
return caches;
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -46,8 +47,8 @@ class ExpressionEvaluator {
private Map<Method, Expression> keyCache = new ConcurrentHashMap<Method, Expression>();
private Map<Method, Method> targetMethodCache = new ConcurrentHashMap<Method, Method>();
EvaluationContext createEvaluationContext(Cache<?, ?> cache, Method method, Object[] args, Class<?> targetClass) {
DefaultCacheExpressionRootObject rootObject = new DefaultCacheExpressionRootObject(cache, method.getName());
EvaluationContext createEvaluationContext(Collection<Cache<?, ?>> caches, Method method, Object[] args, Class<?> targetClass) {
DefaultCacheExpressionRootObject rootObject = new DefaultCacheExpressionRootObject(caches, method.getName());
StandardEvaluationContext evaluationContext = new LazyParamAwareEvaluationContext(rootObject,
paramNameDiscoverer, method, args, targetClass, targetMethodCache);

View File

@ -16,14 +16,19 @@
package org.springframework.cache.support;
import java.lang.reflect.Method;
import org.springframework.cache.KeyGenerator;
/**
* Default key generator. Computes a resulting key based on the hashcode of the
* given parameters.
*
* @author Costin Leau
*/
public class DefaultKeyGenerator implements KeyGenerator<Object> {
public Object extract(Object... params) {
public Object extract(Method method, Object... params) {
int hashCode = 17;
for (Object object : params) {

View File

@ -24,7 +24,7 @@ import org.springframework.cache.annotation.Cacheable;
/**
* @author Costin Leau
*/
@Cacheable
@Cacheable("default")
public class AnnotatedClassCacheableService implements CacheableService {
private AtomicLong counter = new AtomicLong();
@ -37,11 +37,11 @@ public class AnnotatedClassCacheableService implements CacheableService {
return null;
}
@CacheEvict
@CacheEvict("default")
public void invalidate(Object arg1) {
}
@Cacheable(key = "#p0")
@Cacheable(value = "default", key = "#p0")
public Object key(Object arg1, Object arg2) {
return counter.getAndIncrement();
}

View File

@ -31,21 +31,21 @@ public class DefaultCacheableService implements CacheableService<Long> {
private AtomicLong counter = new AtomicLong();
@Cacheable
@Cacheable("default")
public Long cache(Object arg1) {
return counter.getAndIncrement();
}
@CacheEvict
@CacheEvict("default")
public void invalidate(Object arg1) {
}
@Cacheable(condition = "#classField == 3")
@Cacheable(value = "default", condition = "#classField == 3")
public Long conditional(int classField) {
return counter.getAndIncrement();
}
@Cacheable(key = "#p0")
@Cacheable(value = "default", key = "#p0")
public Long key(Object arg1, Object arg2) {
return counter.getAndIncrement();
}