提交 0b917e3f 编写于 作者: C Costin Leau

revise cache API

- eliminate unneeded methods
+ introduced value wrapper (name still to be decided) to avoid cache race conditions
+ improved name consistency
上级 af1dfd35
......@@ -22,7 +22,7 @@ import java.util.concurrent.Callable;
import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.cache.interceptor.CacheDefinitionSource;
import org.springframework.cache.interceptor.CacheOperationSource;
/**
* Abstract superaspect for AspectJ cache aspects. Concrete
......@@ -46,11 +46,11 @@ public abstract aspect AbstractCacheAspect extends CacheAspectSupport {
/**
* Construct object using the given caching metadata retrieval strategy.
* @param cds {@link CacheDefinitionSource} implementation, retrieving Spring
* @param cos {@link CacheOperationSource} implementation, retrieving Spring
* cache metadata for each joinpoint.
*/
protected AbstractCacheAspect(CacheDefinitionSource... cds) {
setCacheDefinitionSources(cds);
protected AbstractCacheAspect(CacheOperationSource... cos) {
setCacheDefinitionSources(cos);
}
@SuppressAjWarnings("adviceDidNotMatch")
......@@ -75,7 +75,7 @@ public abstract aspect AbstractCacheAspect extends CacheAspectSupport {
/**
* Concrete subaspects must implement this pointcut, to identify
* cached methods. For each selected joinpoint, {@link CacheOperationDefinition}
* will be retrieved using Spring's {@link CacheDefinitionSource} interface.
* will be retrieved using Spring's {@link CacheOperationSource} interface.
*/
protected abstract pointcut cacheMethodExecution(Object cachedObject);
}
\ No newline at end of file
......@@ -16,7 +16,7 @@
package org.springframework.cache.aspectj;
import org.springframework.cache.annotation.AnnotationCacheDefinitionSource;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
......@@ -43,7 +43,7 @@ import org.springframework.cache.annotation.Cacheable;
public aspect AnnotationCacheAspect extends AbstractCacheAspect {
public AnnotationCacheAspect() {
super(new AnnotationCacheDefinitionSource(false));
super(new AnnotationCacheOperationSource(false));
}
/**
......
......@@ -28,6 +28,18 @@ package org.springframework.cache;
*/
public interface Cache<K, V> {
/**
* A (wrapper) object representing a cache value.
*/
interface ValueWrapper<V> {
/**
* Returns the actual value in the cache.
*
* @return cache value
*/
V get();
}
/**
* Returns the cache name.
*
......@@ -38,164 +50,36 @@ public interface Cache<K, V> {
/**
* Returns the the native, underlying cache provider.
*
* @return
* @return the underlying native cache provider.
*/
Object getNativeCache();
/**
* Returns <tt>true</tt> if this cache contains a mapping for the specified
* key. More formally, returns <tt>true</tt> if and only if
* this cache contains a mapping for a key <tt>k</tt> such that
* <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
* at most one such mapping.)
*
* @param key key whose presence in this cache is to be tested.
* @return <tt>true</tt> if this cache contains a mapping for the specified
* key.
*/
boolean containsKey(Object key);
/**
* Returns the value to which this cache maps the specified key. Returns
* <tt>null</tt> if the cache contains no mapping for this key. A return
* value of <tt>null</tt> does not <i>necessarily</i> indicate that the
* cache contains no mapping for the key; it's also possible that the cache
* explicitly maps the key to <tt>null</tt>. The <tt>containsKey</tt>
* operation may be used to distinguish these two cases.
*
* <p>More formally, if this cache contains a mapping from a key
* <tt>k</tt> to a value <tt>v</tt> such that <tt>(key==null ? k==null :
* key.equals(k))</tt>, then this method returns <tt>v</tt>; otherwise
* it returns <tt>null</tt>. (There can be at most one such mapping.)
*
* Returns the value to which this cache maps the specified key. Returns
* <tt>null</tt> if the cache contains no mapping for this key.
*
* @param key key whose associated value is to be returned.
* @return the value to which this cache maps the specified key, or
* <tt>null</tt> if the cache contains no mapping for this key.
*
* @see #containsKey(Object)
*/
V get(Object key);
ValueWrapper<V> get(Object key);
/**
* Associates the specified value with the specified key in this cache
* (optional operation). If the cache previously contained a mapping for
* this key, the old value is replaced by the specified value. (A cache
* <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
* if {@link #containsKey(Object) m.containsKey(k)} would return
* <tt>true</tt>.))
* Associates the specified value with the specified key in this cache.
* If the cache previously contained a mapping for this key, the old
* value is replaced by the specified value.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the cache previously associated <tt>null</tt>
* with the specified key, if the implementation supports
* <tt>null</tt> values.
*/
V put(K key, V value);
void put(K key, V value);
/**
* If the specified key is not already associated with a value, associate it with the given value.
*
* This is equivalent to:
* <pre>
* if (!cache.containsKey(key))
* return cache.put(key, value);
* else
* return cache.get(key);
* </pre>
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the cache previously associated <tt>null</tt>
* with the specified key, if the implementation supports
* <tt>null</tt> values.
*/
V putIfAbsent(K key, V value);
/**
* Removes the mapping for this key from this cache if it is present
* (optional operation). More formally, if this cache contains a mapping
* from key <tt>k</tt> to value <tt>v</tt> such that
* <code>(key==null ? k==null : key.equals(k))</code>, that mapping
* is removed. (The cache can contain at most one such mapping.)
*
* <p>Returns the value to which the cache previously associated the key, or
* <tt>null</tt> if the cache contained no mapping for this key. (A
* <tt>null</tt> return can also indicate that the cache previously
* associated <tt>null</tt> with the specified key if the implementation
* supports <tt>null</tt> values.) The cache will not contain a mapping for
* the specified key once the call returns.
* Evicts the mapping for this key from this cache if it is present.
*
* @param key key whose mapping is to be removed from the cache.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key.
*/
V remove(Object key);
/**
* Remove entry for key only if currently mapped to given value.
*
* Similar to:
* <pre>
* if ((cache.containsKey(key) && cache.get(key).equals(value)) {
* cache.remove(key);
* return true;
* }
* else
* return false;
* </pre>
*
* @param key key with which the specified value is associated.
* @param value value associated with the specified key.
* @return true if the value was removed, false otherwise
*/
boolean remove(Object key, Object value);
/**
* Replace entry for key only if currently mapped to given value.
*
* Similar to:
* <pre>
* if ((cache.containsKey(key) && cache.get(key).equals(oldValue)) {
* cache.put(key, newValue);
* return true;
* } else return false;
* </pre>
* @param key key with which the specified value is associated.
* @param oldValue value expected to be associated with the specified key.
* @param newValue value to be associated with the specified key.
* @return true if the value was replaced
*/
boolean replace(K key, V oldValue, V newValue);
/**
* Replace entry for key only if currently mapped to some value.
* Acts as
* <pre>
* if ((cache.containsKey(key)) {
* return cache.put(key, value);
* } else return null;
* </pre>
* except that the action is performed atomically.
* @param key key with which the specified value is associated.
* @param value value to be associated with the specified key.
* @return previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the cache previously associated <tt>null</tt>
* with the specified key, if the implementation supports
* <tt>null</tt> values.
*/
V replace(K key, V value);
void evict(Object key);
/**
* Removes all mappings from the cache.
......
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.annotation;
import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.cache.interceptor.AbstractFallbackCacheDefinitionSource;
import org.springframework.cache.interceptor.CacheDefinition;
import org.springframework.util.Assert;
/**
*
* Implementation of the
* {@link org.springframework.cache.interceptor.CacheDefinitionSource}
* interface for working with caching metadata in JDK 1.5+ annotation format.
*
* <p>This class reads Spring's JDK 1.5+ {@link Cacheable} and {@link CacheEvict}
* annotations and
* exposes corresponding caching operation definition to Spring's cache infrastructure.
* This class may also serve as base class for a custom CacheDefinitionSource.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
public class AnnotationCacheDefinitionSource extends AbstractFallbackCacheDefinitionSource implements
Serializable {
private final boolean publicMethodsOnly;
private final Set<CacheAnnotationParser> annotationParsers;
/**
* Create a default AnnotationCacheOperationDefinitionSource, supporting
* public methods that carry the <code>Cacheable</code> and <code>CacheInvalidate</code>
* annotations.
*/
public AnnotationCacheDefinitionSource() {
this(true);
}
/**
* Create a custom AnnotationCacheOperationDefinitionSource, supporting
* public methods that carry the <code>Cacheable</code> and
* <code>CacheInvalidate</code> annotations.
*
* @param publicMethodsOnly whether to support only annotated public methods
* typically for use with proxy-based AOP), or protected/private methods as well
* (typically used with AspectJ class weaving)
*/
public AnnotationCacheDefinitionSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = new LinkedHashSet<CacheAnnotationParser>(1);
this.annotationParsers.add(new SpringCachingAnnotationParser());
}
/**
* Create a custom AnnotationCacheOperationDefinitionSource.
* @param annotationParsers the CacheAnnotationParser to use
*/
public AnnotationCacheDefinitionSource(CacheAnnotationParser... annotationParsers) {
this.publicMethodsOnly = true;
Assert.notEmpty(annotationParsers, "At least one CacheAnnotationParser needs to be specified");
Set<CacheAnnotationParser> parsers = new LinkedHashSet<CacheAnnotationParser>(annotationParsers.length);
Collections.addAll(parsers, annotationParsers);
this.annotationParsers = parsers;
}
@Override
protected CacheDefinition findCacheDefinition(Class<?> clazz) {
return determineCacheDefinition(clazz);
}
@Override
protected CacheDefinition findCacheOperation(Method method) {
return determineCacheDefinition(method);
}
/**
* Determine the cache operation definition for the given method or class.
* <p>This implementation delegates to configured
* {@link CacheAnnotationParser CacheAnnotationParsers}
* for parsing known annotations into Spring's metadata attribute class.
* Returns <code>null</code> if it's not cacheable.
* <p>Can be overridden to support custom annotations that carry caching metadata.
* @param ae the annotated method or class
* @return CacheOperationDefinition the configured caching operation,
* or <code>null</code> if none was found
*/
protected CacheDefinition determineCacheDefinition(AnnotatedElement ae) {
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
CacheDefinition attr = annotationParser.parseCacheAnnotation(ae);
if (attr != null) {
return attr;
}
}
return null;
}
/**
* By default, only public methods can be made cacheable.
*/
@Override
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
}
\ No newline at end of file
......@@ -18,7 +18,7 @@ package org.springframework.cache.annotation;
import java.lang.reflect.AnnotatedElement;
import org.springframework.cache.interceptor.CacheDefinition;
import org.springframework.cache.interceptor.CacheOperation;
/**
......@@ -38,9 +38,9 @@ public interface CacheAnnotationParser {
* metadata attribute class. Returns <code>null</code> if the method/class
* is not cacheable.
* @param ae the annotated method or class
* @return CacheOperationDefinition the configured caching operation,
* @return CacheOperation the configured caching operation,
* or <code>null</code> if none was found
* @see AnnotationCacheDefinitionSource#determineCacheOperationDefinition
* @see AnnotationCacheOperationSource#determineCacheOperation
*/
CacheDefinition parseCacheAnnotation(AnnotatedElement ae);
CacheOperation parseCacheAnnotation(AnnotatedElement ae);
}
......@@ -20,11 +20,9 @@ import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
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;
import org.springframework.cache.interceptor.CacheEvictOperation;
import org.springframework.cache.interceptor.CacheOperation;
import org.springframework.cache.interceptor.CacheUpdateOperation;
/**
* Strategy implementation for parsing Spring's {@link Cacheable} and {@link CacheEvict} annotations.
......@@ -34,7 +32,7 @@ import org.springframework.cache.interceptor.DefaultCacheUpdateDefinition;
@SuppressWarnings("serial")
public class SpringCachingAnnotationParser implements CacheAnnotationParser, Serializable {
public CacheDefinition parseCacheAnnotation(AnnotatedElement ae) {
public CacheOperation parseCacheAnnotation(AnnotatedElement ae) {
Cacheable update = findAnnotation(ae, Cacheable.class);
if (update != null) {
......@@ -44,7 +42,7 @@ public class SpringCachingAnnotationParser implements CacheAnnotationParser, Ser
CacheEvict invalidate = findAnnotation(ae, CacheEvict.class);
if (invalidate != null) {
return parseInvalidateAnnotation(ae, invalidate);
return parseEvictAnnotation(ae, invalidate);
}
return null;
......@@ -63,8 +61,8 @@ public class SpringCachingAnnotationParser implements CacheAnnotationParser, Ser
return ann;
}
CacheUpdateDefinition parseCacheableAnnotation(AnnotatedElement target, Cacheable ann) {
DefaultCacheUpdateDefinition dcud = new DefaultCacheUpdateDefinition();
CacheUpdateOperation parseCacheableAnnotation(AnnotatedElement target, Cacheable ann) {
CacheUpdateOperation dcud = new CacheUpdateOperation();
dcud.setCacheNames(ann.value());
dcud.setCondition(ann.condition());
dcud.setKey(ann.key());
......@@ -73,8 +71,8 @@ public class SpringCachingAnnotationParser implements CacheAnnotationParser, Ser
return dcud;
}
CacheInvalidateDefinition parseInvalidateAnnotation(AnnotatedElement target, CacheEvict ann) {
DefaultCacheInvalidateDefinition dcid = new DefaultCacheInvalidateDefinition();
CacheEvictOperation parseEvictAnnotation(AnnotatedElement target, CacheEvict ann) {
CacheEvictOperation dcid = new CacheEvictOperation();
dcid.setCacheNames(ann.value());
dcid.setCondition(ann.condition());
dcid.setKey(ann.key());
......
......@@ -54,52 +54,4 @@ public class ConcurrentCache<K, V> extends AbstractDelegatingCache<K, V> {
public ConcurrentMap<K, V> getNativeCache() {
return store;
}
@SuppressWarnings("unchecked")
public V putIfAbsent(K key, V value) {
if (getAllowNullValues()) {
if (value == null) {
ConcurrentMap raw = store;
return filterNull((V) raw.putIfAbsent(key, NULL_HOLDER));
}
}
return filterNull(store.putIfAbsent(key, value));
}
@SuppressWarnings("unchecked")
public boolean remove(Object key, Object value) {
if (getAllowNullValues()) {
if (value == null) {
ConcurrentMap raw = store;
return raw.remove(key, NULL_HOLDER);
}
}
return store.remove(key, value);
}
@SuppressWarnings("unchecked")
public boolean replace(K key, V oldValue, V newValue) {
if (getAllowNullValues()) {
Object rawOldValue = (oldValue == null ? NULL_HOLDER : oldValue);
Object rawNewValue = (newValue == null ? NULL_HOLDER : newValue);
ConcurrentMap raw = store;
return raw.replace(key, rawOldValue, rawNewValue);
}
return store.replace(key, oldValue, newValue);
}
@SuppressWarnings("unchecked")
public V replace(K key, V value) {
if (getAllowNullValues()) {
if (value == null) {
ConcurrentMap raw = store;
return filterNull((V) raw.replace(key, NULL_HOLDER));
}
}
return filterNull(store.replace(key, value));
}
}
\ No newline at end of file
......@@ -16,9 +16,7 @@
package org.springframework.cache.config;
import static org.springframework.context.annotation.AnnotationConfigUtils.CACHE_ADVISOR_BEAN_NAME;
import static org.springframework.context.annotation.AnnotationConfigUtils.CACHE_ASPECT_BEAN_NAME;
import static org.springframework.context.annotation.AnnotationConfigUtils.CACHE_ASPECT_CLASS_NAME;
import static org.springframework.context.annotation.AnnotationConfigUtils.*;
import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
......@@ -28,8 +26,8 @@ import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.cache.annotation.AnnotationCacheDefinitionSource;
import org.springframework.cache.interceptor.BeanFactoryCacheDefinitionSourceAdvisor;
import org.springframework.cache.annotation.AnnotationCacheOperationSource;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.w3c.dom.Element;
......@@ -114,7 +112,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
Object eleSource = parserContext.extractSource(element);
// Create the CacheDefinitionSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationCacheDefinitionSource.class);
RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationCacheOperationSource.class);
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
......@@ -128,7 +126,7 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// Create the CacheAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheDefinitionSourceAdvisor.class);
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheOperationSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("cacheDefinitionSource", new RuntimeBeanReference(sourceName));
......
......@@ -21,6 +21,7 @@ import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.DefaultValue;
import org.springframework.util.Assert;
/**
......@@ -57,77 +58,16 @@ public class EhCacheCache implements Cache<Object, Object> {
cache.removeAll();
}
public boolean containsKey(Object key) {
// get the element to force the expiry check (since #isKeyInCache does not considers that)
Element element = cache.getQuiet(key);
return (element != null ? true : false);
}
public Object get(Object key) {
public ValueWrapper<Object> get(Object key) {
Element element = cache.get(key);
return (element != null ? element.getObjectValue() : null);
return (element != null ? new DefaultValue<Object>(element.getObjectValue()) : null);
}
public Object put(Object key, Object value) {
Element previous = cache.getQuiet(key);
public void put(Object key, Object value) {
cache.put(new Element(key, value));
return (previous != null ? previous.getValue() : null);
}
public Object remove(Object key) {
Element element = cache.getQuiet(key);
Object value = (element != null ? element.getObjectValue() : null);
public void evict(Object key) {
cache.remove(key);
return value;
}
public Object putIfAbsent(Object key, Object value) {
// putIfAbsent supported only from Ehcache 2.1
// return cache.putIfAbsent(new Element(key, value));
Element existing = cache.getQuiet(key);
if (existing == null) {
cache.put(new Element(key, value));
return null;
}
return existing.getObjectValue();
}
public boolean remove(Object key, Object value) {
// remove(Element) supported only from Ehcache 2.1
// return cache.removeElement(new Element(key, value));
Element existing = cache.getQuiet(key);
if (existing != null && existing.getObjectValue().equals(value)) {
cache.remove(key);
return true;
}
return false;
}
public Object replace(Object key, Object value) {
// replace(Object, Object) supported only from Ehcache 2.1
// return cache.replace(new Element(key, value));
Element existing = cache.getQuiet(key);
if (existing != null) {
cache.put(new Element(key, value));
return existing.getObjectValue();
}
return null;
}
public boolean replace(Object key, Object oldValue, Object newValue) {
// replace(Object, Object, Object) supported only from Ehcache 2.1
// return cache.replace(new Element(key, oldValue), new Element(key,
// newValue));
Element existing = cache.getQuiet(key);
if (existing != null && existing.getObjectValue().equals(oldValue)) {
cache.put(new Element(key, newValue));
return true;
}
return false;
}
}
\ No newline at end of file
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.util.Assert;
/**
* Base class implementing {@link CacheDefinition}.
*
* @author Costin Leau
*/
abstract class AbstractCacheDefinition implements CacheDefinition {
private Set<String> cacheNames = Collections.emptySet();
private String condition = "";
private String key = "";
private String name = "";
public Set<String> getCacheNames() {
return cacheNames;
}
public String getCondition() {
return condition;
}
public String getKey() {
return key;
}
public String getName() {
return name;
}
public void setCacheName(String 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;
}
/**
* This implementation compares the <code>toString()</code> results.
* @see #toString()
*/
@Override
public boolean equals(Object other) {
return (other instanceof CacheDefinition && toString().equals(other.toString()));
}
/**
* This implementation returns <code>toString()</code>'s hash code.
* @see #toString()
*/
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Return an identifying description for this cache operation definition.
* <p>Has to be overridden in subclasses for correct <code>equals</code>
* and <code>hashCode</code> behavior. Alternatively, {@link #equals}
* and {@link #hashCode} can be overridden themselves.
*/
@Override
public String toString() {
return getDefinitionDescription().toString();
}
/**
* Return an identifying description for this caching definition.
* <p>Available to subclasses, for inclusion in their <code>toString()</code> result.
*/
protected StringBuilder getDefinitionDescription() {
StringBuilder result = new StringBuilder();
result.append("CacheDefinition[");
result.append(name);
result.append("] caches=");
result.append(cacheNames);
result.append(" | condition='");
result.append(condition);
result.append("' | key='");
result.append(key);
result.append("'");
return result;
}
}
\ No newline at end of file
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Abstract implementation of {@link CacheDefinition} that caches
* attributes for methods and implements a fallback policy: 1. specific target
* method; 2. target class; 3. declaring method; 4. declaring class/interface.
*
* <p>Defaults to using the target class's caching attribute if none is
* associated with the target method. Any caching attribute associated with
* the target method completely overrides a class caching attribute.
* If none found on the target class, the interface that the invoked method
* has been called through (in case of a JDK proxy) will be checked.
*
* <p>This implementation caches attributes by method after they are first used.
* If it is ever desirable to allow dynamic changing of cacheable attributes
* (which is very unlikely), caching could be made configurable.
* @author Costin Leau
* @see org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource
*/
public abstract class AbstractFallbackCacheDefinitionSource implements CacheDefinitionSource {
/**
* Canonical value held in cache to indicate no caching attribute was
* found for this method, and we don't need to look again.
*/
private final static CacheDefinition NULL_CACHING_ATTRIBUTE = new DefaultCacheUpdateDefinition();
/**
* Logger available to subclasses.
* <p>As this base class is not marked Serializable, the logger will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Cache of CacheOperationDefinitions, keyed by DefaultCacheKey (Method + target Class).
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
final Map<Object, CacheDefinition> attributeCache = new ConcurrentHashMap<Object, CacheDefinition>();
/**
* Determine the caching attribute for this method invocation.
* <p>Defaults to the class's caching attribute if no method attribute is found.
* @param method the method for the current invocation (never <code>null</code>)
* @param targetClass the target class for this invocation (may be <code>null</code>)
* @return {@link CacheDefinition} for this method, or <code>null</code> if the method
* is not cacheable
*/
public CacheDefinition getCacheDefinition(Method method, Class<?> targetClass) {
// First, see if we have a cached value.
Object cacheKey = getCacheKey(method, targetClass);
CacheDefinition cached = this.attributeCache.get(cacheKey);
if (cached != null) {
if (cached == NULL_CACHING_ATTRIBUTE) {
return null;
}
// Value will either be canonical value indicating there is no caching attribute,
// or an actual caching attribute.
return cached;
}
else {
// We need to work it out.
CacheDefinition cacheDef = computeCacheOperationDefinition(method, targetClass);
// Put it in the cache.
if (cacheDef == null) {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheDef);
}
this.attributeCache.put(cacheKey, cacheDef);
}
return cacheDef;
}
}
/**
* Determine a cache key for the given method and target class.
* <p>Must not produce same key for overloaded methods.
* Must produce same key for different instances of the same method.
* @param method the method (never <code>null</code>)
* @param targetClass the target class (may be <code>null</code>)
* @return the cache key (never <code>null</code>)
*/
protected Object getCacheKey(Method method, Class<?> targetClass) {
return new DefaultCacheKey(method, targetClass);
}
/**
* Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
* {@link #getTransactionAttribute} is effectively a caching decorator for this method.
* @see #getTransactionAttribute
*/
private CacheDefinition computeCacheOperationDefinition(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// First try is the method in the target class.
CacheDefinition opDef = findCacheOperation(specificMethod);
if (opDef != null) {
return opDef;
}
// Second try is the caching operation on the target class.
opDef = findCacheDefinition(specificMethod.getDeclaringClass());
if (opDef != null) {
return opDef;
}
if (specificMethod != method) {
// Fall back is to look at the original method.
opDef = findCacheOperation(method);
if (opDef != null) {
return opDef;
}
// Last fall back is the class of the original method.
return findCacheDefinition(method.getDeclaringClass());
}
return null;
}
/**
* Subclasses need to implement this to return the caching attribute
* for the given method, if any.
* @param method the method to retrieve the attribute for
* @return all caching attribute associated with this method
* (or <code>null</code> if none)
*/
protected abstract CacheDefinition findCacheOperation(Method method);
/**
* Subclasses need to implement this to return the caching attribute
* for the given class, if any.
* @param clazz the class to retrieve the attribute for
* @return all caching attribute associated with this class
* (or <code>null</code> if none)
*/
protected abstract CacheDefinition findCacheDefinition(Class<?> clazz);
/**
* Should only public methods be allowed to have caching semantics?
* <p>The default implementation returns <code>false</code>.
*/
protected boolean allowPublicMethodsOnly() {
return false;
}
/**
* Default cache key for the CacheOperationDefinition cache.
*/
private static class DefaultCacheKey {
private final Method method;
private final Class<?> targetClass;
public DefaultCacheKey(Method method, Class<?> targetClass) {
this.method = method;
this.targetClass = targetClass;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof DefaultCacheKey)) {
return false;
}
DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass,
otherKey.targetClass));
}
@Override
public int hashCode() {
return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
}
}
}
\ No newline at end of file
/*
* Copyright 2010-2011 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.aop.ClassFilter;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor;
/**
* Advisor driven by a {@link CacheDefinitionSource}, used to include a
* transaction advice bean for methods that are transactional.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
public class BeanFactoryCacheDefinitionSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
private CacheDefinitionSource cacheDefinitionSource;
private final CacheDefinitionSourcePointcut pointcut = new CacheDefinitionSourcePointcut() {
@Override
protected CacheDefinitionSource getCacheDefinitionSource() {
return cacheDefinitionSource;
}
};
/**
* Set the cache operation attribute source which is used to find cache
* attributes. This should usually be identical to the source reference
* set on the cache interceptor itself.
* @see CacheInterceptor#setCacheAttributeSource
*/
public void setCacheDefinitionSource(CacheDefinitionSource cacheDefinitionSource) {
this.cacheDefinitionSource = cacheDefinitionSource;
}
/**
* Set the {@link ClassFilter} to use for this pointcut.
* Default is {@link ClassFilter#TRUE}.
*/
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
public Pointcut getPointcut() {
return this.pointcut;
}
}
\ No newline at end of file
......@@ -64,7 +64,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
private CacheManager cacheManager;
private CacheDefinitionSource cacheDefinitionSource;
private CacheOperationSource cacheDefinitionSource;
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
......@@ -102,7 +102,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
this.cacheManager = cacheManager;
}
public CacheDefinitionSource getCacheDefinitionSource() {
public CacheOperationSource getCacheDefinitionSource() {
return cacheDefinitionSource;
}
......@@ -118,21 +118,21 @@ public abstract class CacheAspectSupport implements InitializingBean {
* Set multiple cache definition sources which are used to find the cache
* attributes. Will build a CompositeCachingDefinitionSource for the given sources.
*/
public void setCacheDefinitionSources(CacheDefinitionSource... cacheDefinitionSources) {
public void setCacheDefinitionSources(CacheOperationSource... cacheDefinitionSources) {
Assert.notEmpty(cacheDefinitionSources);
this.cacheDefinitionSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheDefinitionSource(
this.cacheDefinitionSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheOperationSource(
cacheDefinitionSources) : cacheDefinitionSources[0]);
}
protected Collection<Cache<?, ?>> getCaches(CacheDefinition definition) {
Set<String> cacheNames = definition.getCacheNames();
protected Collection<Cache<?, ?>> getCaches(CacheOperation operation) {
Set<String> cacheNames = operation.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);
throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + operation);
}
caches.add(cache);
}
......@@ -140,9 +140,9 @@ public abstract class CacheAspectSupport implements InitializingBean {
return caches;
}
protected CacheOperationContext getOperationContext(CacheDefinition definition, Method method, Object[] args,
protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args,
Object target, Class<?> targetClass) {
return new CacheOperationContext(definition, method, args, target, targetClass);
return new CacheOperationContext(operation, method, args, target, targetClass);
}
@SuppressWarnings("unchecked")
......@@ -156,112 +156,63 @@ public abstract class CacheAspectSupport implements InitializingBean {
boolean log = logger.isTraceEnabled();
final CacheDefinition cacheDef = getCacheDefinitionSource().getCacheDefinition(method, targetClass);
final CacheOperation cacheOp = getCacheDefinitionSource().getCacheOperation(method, targetClass);
Object retVal = null;
// analyze caching information
if (cacheDef != null) {
CacheOperationContext context = getOperationContext(cacheDef, method, args, target, targetClass);
if (cacheOp != null) {
CacheOperationContext context = getOperationContext(cacheOp, method, args, target, targetClass);
Collection<Cache<?, ?>> caches = context.getCaches();
if (context.hasConditionPassed()) {
// check operation
if (cacheDef instanceof CacheUpdateDefinition) {
if (cacheOp instanceof CacheUpdateOperation) {
Object key = context.generateKey();
if (log) {
logger.trace("Computed cache key " + key + " for definition " + cacheDef);
logger.trace("Computed cache key " + key + " for definition " + cacheOp);
}
if (key == null) {
throw new IllegalArgumentException(
"Null key returned for cache definition (maybe you are using named params on classes without debug info?) "
+ cacheDef);
+ cacheOp);
}
//
// check usage of single cache
// very common case which allows for some optimization
// in exchange for code readability
//
// for each cache
boolean cacheHit = false;
if (caches.size() == 1) {
Cache cache = caches.iterator().next();
for (Iterator<Cache<?, ?>> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
Cache cache = iterator.next();
Cache.ValueWrapper<Object> value = cache.get(key);
// always get the value
retVal = cache.get(key);
// to avoid race-conditions of entries being removed between contains/get calls
if (cache.containsKey(key)) {
if (log) {
logger.trace("Key " + key + " found in cache, returning value " + retVal);
}
return retVal;
} else {
if (log) {
logger.trace("Key " + key + " NOT found in cache, invoking target method for caching "
+ method);
}
retVal = invocation.call();
cache.put(key, retVal);
if (value != null) {
cacheHit = true;
retVal = value.get();
}
}
//
// multi cache path
//
else {
// to avoid the contains/get race condition we can:
// a. get the value in advanced (aka 'eagerGet')
// b. double check 'contains' before and after get
// a implies more calls in total if more then 3 caches are used (n*2 calls)
// b uses less calls in total but is 1 call heavier for one cache (n+2 calls)
// --
// for balance, a) is used for up to 3 caches, b for more then 4
boolean eagerGet = caches.size() <= 3;
// for each cache
boolean cacheHit = false;
for (Iterator<Cache<?, ?>> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
Cache cache = iterator.next();
if (eagerGet) {
retVal = cache.get(key);
}
if (cache.containsKey(key)) {
if (eagerGet) {
cacheHit = true;
} else {
retVal = cache.get(key);
cacheHit = cache.containsKey(key);
}
}
if (!cacheHit) {
if (log) {
logger.trace("Key " + key + " NOT found in cache(s), invoking cached target method "
+ method);
}
if (!cacheHit) {
if (log) {
logger.trace("Key " + key + " NOT found in cache(s), invoking cached target method "
+ method);
}
retVal = invocation.call();
}
else {
if (log) {
logger.trace("Key " + key + " found in cache, returning value " + retVal);
}
retVal = invocation.call();
} else {
if (log) {
logger.trace("Key " + key + " found in cache, returning value " + retVal);
}
}
// update all caches (if needed)
for (Cache cache : caches) {
cache.putIfAbsent(key, retVal);
}
// update all caches
for (Cache cache : caches) {
cache.put(key, retVal);
}
}
if (cacheDef instanceof CacheInvalidateDefinition) {
CacheInvalidateDefinition invalidateDef = (CacheInvalidateDefinition) cacheDef;
if (cacheOp instanceof CacheEvictOperation) {
CacheEvictOperation evictOp = (CacheEvictOperation) cacheOp;
retVal = invocation.call();
// for each cache
......@@ -270,10 +221,10 @@ public abstract class CacheAspectSupport implements InitializingBean {
for (Cache cache : caches) {
// flush the cache (ignore arguments)
if (invalidateDef.isCacheWide()) {
if (evictOp.isCacheWide()) {
cache.clear();
if (log) {
logger.trace("Invalidating entire cache for definition " + cacheDef + " on method "
logger.trace("Invalidating entire cache for definition " + cacheOp + " on method "
+ method);
}
} else {
......@@ -282,19 +233,18 @@ public abstract class CacheAspectSupport implements InitializingBean {
key = context.generateKey();
}
if (log) {
logger.trace("Invalidating cache key " + key + " for definition " + cacheDef
logger.trace("Invalidating cache key " + key + " for definition " + cacheOp
+ " on method " + method);
}
cache.remove(key);
cache.evict(key);
}
}
}
return retVal;
}
else {
} else {
if (log) {
logger.trace("Cache condition failed on method " + method + " for definition " + cacheDef);
logger.trace("Cache condition failed on method " + method + " for definition " + cacheOp);
}
}
}
......@@ -304,7 +254,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
protected class CacheOperationContext {
private CacheDefinition definition;
private CacheOperation operation;
private final Collection<Cache<?, ?>> caches;
private final Object target;
private final Method method;
......@@ -315,10 +265,10 @@ public abstract class CacheAspectSupport implements InitializingBean {
private final KeyGenerator<?> keyGenerator = CacheAspectSupport.this.keyGenerator;
public CacheOperationContext(CacheDefinition operationDefinition, Method method, Object[] args,
Object target, Class<?> targetClass) {
this.definition = operationDefinition;
this.caches = CacheAspectSupport.this.getCaches(definition);
public CacheOperationContext(CacheOperation operation, Method method, Object[] args, Object target,
Class<?> targetClass) {
this.operation = operation;
this.caches = CacheAspectSupport.this.getCaches(operation);
this.target = target;
this.method = method;
this.args = args;
......@@ -329,12 +279,12 @@ public abstract class CacheAspectSupport implements InitializingBean {
/**
* Evaluates the definition condition.
*
* @param definition
* @param operation
* @return
*/
protected boolean hasConditionPassed() {
if (StringUtils.hasText(definition.getCondition())) {
return evaluator.condition(definition.getCondition(), method, evalContext);
if (StringUtils.hasText(operation.getCondition())) {
return evaluator.condition(operation.getCondition(), method, evalContext);
}
return true;
}
......@@ -342,7 +292,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
/**
* Computes the key for the given caching definition.
*
* @param definition
* @param operation
* @param method
* method being invoked
* @param objects
......@@ -350,8 +300,8 @@ public abstract class CacheAspectSupport implements InitializingBean {
* @return generated key (null if none can be generated)
*/
protected Object generateKey() {
if (StringUtils.hasText(definition.getKey())) {
return evaluator.key(definition.getKey(), method, evalContext);
if (StringUtils.hasText(operation.getKey())) {
return evaluator.key(operation.getKey(), method, evalContext);
}
return keyGenerator.extract(target, method, args);
......
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.util.Set;
/**
* Interface describing Spring-compliant caching operation.
*
* @author Costin Leau
*/
public interface CacheDefinition {
/**
* Returns the name of this operation. Can be <tt>null</tt>.
* In case of Spring's declarative caching, the exposed name will be:
* <tt>fully qualified class name.method name</tt>.
*
* @return the operation name
*/
String getName();
/**
* Returns the names of the cache against which this operation is performed.
*
* @return names of the cache on which the operation is performed.
*/
Set<String> getCacheNames();
/**
* Returns the SpEL expression conditioning the operation.
*
* @return operation condition (as SpEL expression).
*/
String getCondition();
/**
* Returns the SpEL expression identifying the cache key.
*
* @return
*/
String getKey();
}
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.lang.reflect.Method;
/**
* Interface used by CacheInterceptor. Implementations know
* how to source cache operation attributes, whether from configuration,
* metadata attributes at source level, or anywhere else.
*
* @author Costin Leau
*/
public interface CacheDefinitionSource {
/**
* Return the cache operation definition for this method.
* Return null if the method is not cacheable.
* @param method method
* @param targetClass target class. May be <code>null</code>, in which
* case the declaring class of the method must be used.
* @return {@link CacheDefinition} the matching cache operation definition,
* or <code>null</code> if none found
*/
CacheDefinition getCacheDefinition(Method method, Class<?> targetClass);
}
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.util.ObjectUtils;
/**
* Inner class that implements a Pointcut that matches if the underlying
* {@link CacheDefinitionSource} has an attribute for a given method.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
abstract class CacheDefinitionSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
public boolean matches(Method method, Class<?> targetClass) {
CacheDefinitionSource cas = getCacheDefinitionSource();
return (cas == null || cas.getCacheDefinition(method, targetClass) != null);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof CacheDefinitionSourcePointcut)) {
return false;
}
CacheDefinitionSourcePointcut otherPc = (CacheDefinitionSourcePointcut) other;
return ObjectUtils.nullSafeEquals(getCacheDefinitionSource(),
otherPc.getCacheDefinitionSource());
}
@Override
public int hashCode() {
return CacheDefinitionSourcePointcut.class.hashCode();
}
@Override
public String toString() {
return getClass().getName() + ": " + getCacheDefinitionSource();
}
/**
* Obtain the underlying CacheOperationDefinitionSource (may be <code>null</code>).
* To be implemented by subclasses.
*/
protected abstract CacheDefinitionSource getCacheDefinitionSource();
}
\ No newline at end of file
/*
* Copyright 2010-2011 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;
/**
* Interface describing a Spring cache invalidation.
*
* @author Costin Leau
*/
public interface CacheInvalidateDefinition extends CacheDefinition {
/**
* Returns whether the operation affects the entire cache or not.
*
* @return whether the operation is cache wide or not.
*/
boolean isCacheWide();
}
......@@ -49,7 +49,7 @@ public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
*
* @param cacheDefinitionSources cache definition sources
*/
public void setCacheDefinitionSources(CacheDefinitionSource... cacheDefinitionSources) {
public void setCacheDefinitionSources(CacheOperationSource... cacheDefinitionSources) {
this.cachingInterceptor.setCacheDefinitionSources(cacheDefinitionSources);
}
......
/*
* Copyright 2010-2011 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;
/**
* Interface describing a Spring cache update.
*
* @author Costin Leau
*/
public interface CacheUpdateDefinition extends CacheDefinition {
/**
* Returns the SpEL expression identifying the cache key.
*
* @return
*/
String getKey();
}
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.interceptor;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.util.Assert;
/**
* Composite {@link CacheDefinitionSource} implementation that iterates
* over a given array of {@link CacheDefinitionSource} instances.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
public class CompositeCacheDefinitionSource implements CacheDefinitionSource, Serializable {
private final CacheDefinitionSource[] cacheDefinitionSources;
/**
* Create a new CompositeCachingDefinitionSource for the given sources.
* @param cacheDefinitionSourcess the CacheDefinitionSource instances to combine
*/
public CompositeCacheDefinitionSource(CacheDefinitionSource[] cacheDefinitionSources) {
Assert.notNull(cacheDefinitionSources, "cacheDefinitionSource array must not be null");
this.cacheDefinitionSources = cacheDefinitionSources;
}
/**
* Return the CacheDefinitionSource instances that this
* CompositeCachingDefinitionSource combines.
*/
public final CacheDefinitionSource[] getCacheDefinitionSources() {
return this.cacheDefinitionSources;
}
public CacheDefinition getCacheDefinition(Method method, Class<?> targetClass) {
for (CacheDefinitionSource source : cacheDefinitionSources) {
CacheDefinition definition = source.getCacheDefinition(method, targetClass);
if (definition != null) {
return definition;
}
}
return null;
}
}
/*
* Copyright 2010-2011 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;
/**
* Default implementation of the {@link CacheInvalidateDefinition} interface.
*
* @author Costin Leau
*/
public class DefaultCacheInvalidateDefinition extends AbstractCacheDefinition implements
CacheInvalidateDefinition {
private boolean cacheWide = false;
public boolean isCacheWide() {
return cacheWide;
}
public void setCacheWide(boolean cacheWide) {
this.cacheWide = cacheWide;
}
@Override
protected StringBuilder getDefinitionDescription() {
StringBuilder sb = super.getDefinitionDescription();
sb.append(",");
sb.append(cacheWide);
return sb;
}
}
/*
* Copyright 2010-2011 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;
/**
* Default implementation of the {@link CacheUpdateDefinition} interface.
*
* @author Costin Leau
*/
public class DefaultCacheUpdateDefinition extends AbstractCacheDefinition implements CacheUpdateDefinition {
}
......@@ -20,14 +20,16 @@ import java.io.Serializable;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.cache.interceptor.DefaultValue;
import org.springframework.util.Assert;
/**
* Abstract base class delegating most of the {@link Map}-like methods
* to the underlying cache.
*
* <b>Note:</b>Allows null values to be stored, even if the underlying map
* does not support them.
* <b>Note:</b>Allows null values to be stored even (for cases where the
* underlying cache does not support them) as long as arbitrary serialized
* objects are supported.
*
* @author Costin Leau
*/
......@@ -57,7 +59,7 @@ public abstract class AbstractDelegatingCache<K, V> implements Cache<K, V> {
*
* @param <D> map type
* @param delegate map delegate
* @param allowNullValues flag indicating whether null values are allowed or not
* @param allowNullValues flag indicating whether null values should be replaced or not
*/
public <D extends Map<K, V>> AbstractDelegatingCache(D delegate, boolean allowNullValues) {
Assert.notNull(delegate);
......@@ -73,30 +75,22 @@ public abstract class AbstractDelegatingCache<K, V> implements Cache<K, V> {
delegate.clear();
}
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
public V get(Object key) {
return filterNull(delegate.get(key));
public ValueWrapper<V> get(Object key) {
return new DefaultValue<V>(filterNull(delegate.get(key)));
}
@SuppressWarnings("unchecked")
public V put(K key, V value) {
public void put(K key, V value) {
if (allowNullValues && value == null) {
Map map = delegate;
Object val = map.put(key, NULL_HOLDER);
if (val == NULL_HOLDER) {
return null;
}
return (V) val;
map.put(key, NULL_HOLDER);
}
return filterNull(delegate.put(key, value));
delegate.put(key, value);
}
public V remove(Object key) {
return filterNull(delegate.remove(key));
public void evict(Object key) {
delegate.remove(key);
}
protected V filterNull(V val) {
......
......@@ -54,11 +54,9 @@ public class EhCacheCacheTest extends AbstractNativeCacheTest<Ehcache> {
brancusi.setTimeToLive(3);
nativeCache.put(brancusi);
assertTrue(cache.containsKey(key));
assertEquals(value, cache.get(key));
assertEquals(value, cache.get(key).get());
// wait for the entry to expire
Thread.sleep(5 * 1000);
assertFalse("expired entry returned", cache.containsKey(key));
assertNull(cache.get(key));
}
}
\ No newline at end of file
......@@ -58,7 +58,6 @@ public abstract class AbstractNativeCacheTest<T> {
@Test
public void testCachePut() throws Exception {
Object key = "enescu";
Object value = "george";
......@@ -74,8 +73,6 @@ public abstract class AbstractNativeCacheTest<T> {
assertNull(cache.get(key));
cache.put(key, value);
assertEquals(value, cache.remove(key));
assertNull(cache.get(key));
}
@Test
......@@ -88,66 +85,4 @@ public abstract class AbstractNativeCacheTest<T> {
assertNull(cache.get("vlaicu"));
assertNull(cache.get("enescu"));
}
// concurrent map tests
@Test
public void testPutIfAbsent() throws Exception {
Object key = "enescu";
Object value1 = "george";
Object value2 = "geo";
assertNull(cache.get("enescu"));
cache.put(key, value1);
cache.putIfAbsent(key, value2);
assertEquals(value1, cache.get(key));
}
@Test
public void testConcurrentRemove() throws Exception {
Object key = "enescu";
Object value1 = "george";
Object value2 = "geo";
assertNull(cache.get("enescu"));
cache.put(key, value1);
// no remove
cache.remove(key, value2);
assertEquals(value1, cache.get(key));
// one remove
cache.remove(key, value1);
assertNull(cache.get("enescu"));
}
@Test
public void testConcurrentReplace() throws Exception {
Object key = "enescu";
Object value1 = "george";
Object value2 = "geo";
assertNull(cache.get("enescu"));
cache.put(key, value1);
cache.replace(key, value2);
assertEquals(value2, cache.get(key));
cache.remove(key);
cache.replace(key, value1);
assertNull(cache.get("enescu"));
}
@Test
public void testConcurrentReplaceIfEqual() throws Exception {
Object key = "enescu";
Object value1 = "george";
Object value2 = "geo";
assertNull(cache.get("enescu"));
cache.put(key, value1);
assertEquals(value1, cache.get(key));
// no replace
cache.replace(key, value2, value1);
assertEquals(value1, cache.get(key));
cache.replace(key, value1, value2);
assertEquals(value2, cache.get(key));
cache.replace(key, value2, value1);
assertEquals(value1, cache.get(key));
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册