+ initial commit of caching abstraction
+ main API
+ Spring AOP and AspectJ support
+ annotation driven, declarative support
+ initial namespace draft
This commit is contained in:
Costin Leau
2010-10-29 17:00:08 +00:00
parent 416777022d
commit 85c02981b5
62 changed files with 4225 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
/*
* Copyright 2002-2009 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;
/**
* Interface that defines the common cache operations.
*
* @author Costin Leau
*/
public interface Cache<K, V> {
/**
* Returns the cache name.
*
* @return the cache name.
*/
String getName();
/**
* Returns the the native, underlying cache provider.
*
* @return
*/
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.)
*
* @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);
/**
* 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>.))
*
* @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);
/**
* 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.
*
* @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);
/**
* Removes all mappings from the cache.
*/
void clear();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2009 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;
import java.util.Collection;
/**
* Entity managing {@link Cache}s.
*
* @author Costin Leau
*/
public interface CacheManager {
/**
* Returns the cache associated with the given name.
*
* @param name cache identifier - cannot be null
* @return associated cache or null if none is found
*/
<K, V> Cache<K, V> getCache(String name);
/**
* Returns a collection of the caches known by this cache manager.
*
* @return names of caches known by the cache manager.
*/
Collection<String> getCacheNames();
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2002-2009 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;
/**
* Cache 'key' extractor. Used for creating a key based on the given
* parameters.
*
* @author Costin Leau
*/
// CL: could be renamed to KeyFactory
public interface KeyGenerator<K> {
K extract(Object... params);
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2010 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.parseTransactionAnnotation(ae);
if (attr != null) {
return attr;
}
}
return null;
}
/**
* By default, only public methods can be made cacheable.
*/
@Override
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2010 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.lang.reflect.AnnotatedElement;
import org.springframework.cache.interceptor.CacheDefinition;
/**
* Strategy interface for parsing known caching annotation types.
* {@link AnnotationCacheDefinitionSource} delegates to such
* parsers for supporting specific annotation types such as Spring's own
* {@link Cacheable} or {@link CacheEvict}.
*
* @author Costin Leau
*/
public interface CacheAnnotationParser {
/**
* Parses the cache definition for the given method or class,
* based on a known annotation type.
* <p>This essentially parses a known cache annotation into Spring's
* 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,
* or <code>null</code> if none was found
* @see AnnotationCacheDefinitionSource#determineCacheOperationDefinition
*/
CacheDefinition parseTransactionAnnotation(AnnotatedElement ae);
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2009 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that a method (or all methods on a class) trigger(s)
* a cache invalidate operation.
*
* @author Costin Leau
*/
@Target( { ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
/**
* Qualifier value for the specified cached operation.
* <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 "";
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p/>
* Default is "" meaning all method parameters are considered as a key.
*/
String key() default "";
/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
* <p/>
* Default is "" meaning the method is always cached.
*/
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.
*
* Note that specifying setting this parameter to true and specifying a
* {@link CacheKey key} is not allowed.
*/
boolean allEntries() default false;
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2002-2009 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation indicating that a method (or all the methods on a class) can be cached.
* The method arguments and signature are used for computing the key while the return instance
* as the cache value.
*
* @author Costin Leau
*/
@Target( { ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
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.
*/
String value() default "";
/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p/>
* Default is "" meaning all method parameters are considered as a key.
*/
String key() default "";
/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
* <p/>
* Default is "" meaning the method is always cached.
*/
String condition() default "";
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2010 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.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import org.springframework.cache.interceptor.CacheInvalidateDefinition;
import org.springframework.cache.interceptor.CacheDefinition;
import org.springframework.cache.interceptor.CacheUpdateDefinition;
import org.springframework.cache.interceptor.DefaultCacheInvalidateDefinition;
import org.springframework.cache.interceptor.DefaultCacheUpdateDefinition;
/**
* Strategy implementation for parsing Spring's {@link Cacheable} and {@link CacheEvict} annotations.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
public class SpringCachingAnnotationParser implements CacheAnnotationParser, Serializable {
public CacheDefinition parseTransactionAnnotation(AnnotatedElement ae) {
Cacheable update = findAnnotation(ae, Cacheable.class);
if (update != null) {
return parseCacheableAnnotation(update);
}
CacheEvict invalidate = findAnnotation(ae, CacheEvict.class);
if (invalidate != null) {
return parseInvalidateAnnotation(invalidate);
}
return null;
}
private <T extends Annotation> T findAnnotation(AnnotatedElement ae, Class<T> annotationType) {
T ann = ae.getAnnotation(annotationType);
if (ann == null) {
for (Annotation metaAnn : ae.getAnnotations()) {
ann = metaAnn.annotationType().getAnnotation(annotationType);
if (ann != null) {
break;
}
}
}
return ann;
}
public CacheUpdateDefinition parseCacheableAnnotation(Cacheable ann) {
DefaultCacheUpdateDefinition dcud = new DefaultCacheUpdateDefinition();
dcud.setCacheName(ann.value());
dcud.setCondition(ann.condition());
dcud.setKey(ann.key());
return dcud;
}
public CacheInvalidateDefinition parseInvalidateAnnotation(CacheEvict ann) {
DefaultCacheInvalidateDefinition dcid = new DefaultCacheInvalidateDefinition();
dcid.setCacheName(ann.value());
dcid.setCondition(ann.condition());
dcid.setKey(ann.key());
dcid.setCacheWide(ann.allEntries());
return dcid;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2010 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.concurrent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractDelegatingCache;
/**
* Simple {@link Cache} implementation based on the JDK 1.5+ java.util.concurrent package.
* Useful for testing or simple caching scenarios.
*
* @author Costin Leau
*/
public class ConcurrentCache<K, V> extends AbstractDelegatingCache<K, V> {
private final ConcurrentMap<K, V> store;
private final String name;
public ConcurrentCache() {
this("");
}
public ConcurrentCache(String name) {
this(new ConcurrentHashMap<K, V>(), name);
}
public ConcurrentCache(ConcurrentMap<K, V> delegate, String name) {
super(delegate);
this.store = delegate;
this.name = name;
}
public String getName() {
return name;
}
public ConcurrentMap<K, V> getNativeCache() {
return store;
}
public V putIfAbsent(K key, V value) {
return store.putIfAbsent(key, value);
}
public boolean remove(Object key, Object value) {
return store.remove(key, value);
}
public boolean replace(K key, V oldValue, V newValue) {
return store.replace(key, oldValue, newValue);
}
public V replace(K key, V value) {
return store.replace(key, value);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2010 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.concurrent;
import java.util.concurrent.ConcurrentMap;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Factory bean for easy configuration of {@link ConcurrentCache} through Spring.
*
* @author Costin Leau
*/
public class ConcurrentCacheFactoryBean<K, V> implements FactoryBean<ConcurrentCache<K, V>>, BeanNameAware,
InitializingBean {
private String name = "";
private ConcurrentCache<K, V> cache;
private ConcurrentMap<K, V> store;
public void afterPropertiesSet() {
cache = (store == null ? new ConcurrentCache<K, V>(name) : new ConcurrentCache<K, V>(store, name));
}
public ConcurrentCache<K, V> getObject() throws Exception {
return cache;
}
public Class<?> getObjectType() {
return (cache != null ? cache.getClass() : ConcurrentCache.class);
}
public boolean isSingleton() {
return true;
}
public void setBeanName(String beanName) {
setName(beanName);
}
public void setName(String name) {
this.name = name;
}
public void setStore(ConcurrentMap<K, V> store) {
this.store = store;
}
}

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2010 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.config;
import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
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.interceptor.CacheInterceptor;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
* implementation that allows users to easily configure all the infrastructure
* beans required to enable annotation-driven cache demarcation.
*
* <p>By default, all proxies are created as JDK proxies. This may cause some
* problems if you are injecting objects as concrete classes rather than
* interfaces. To overcome this restriction you can set the
* '<code>proxy-target-class</code>' attribute to '<code>true</code>', which
* will result in class-based proxies being created.
*
* @author Costin Leau
*/
class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser {
private static final String CACHE_MANAGER_ATTRIBUTE = "cache-manager";
private static final String DEFAULT_CACHE_MANAGER_BEAN_NAME = "cacheManager";
/**
* The bean name of the internally managed cache advisor (mode="proxy").
*/
public static final String CACHE_ADVISOR_BEAN_NAME = "org.springframework.cache.config.internalCacheAdvisor";
/**
* The bean name of the internally managed cache aspect (mode="aspectj").
*/
public static final String CACHE_ASPECT_BEAN_NAME = "org.springframework.cache.config.internalCacheAspect";
private static final String CACHE_ASPECT_CLASS_NAME = "org.springframework.cache.aspectj.AnnotationCacheAspect";
/**
* Parses the '<code>&lt;cache:annotation-driven/&gt;</code>' tag. Will
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
* with the container as necessary.
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerCacheAspect(element, parserContext);
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}
private static String extractCacheManager(Element element) {
return (element.hasAttribute(CACHE_MANAGER_ATTRIBUTE) ? element.getAttribute(CACHE_MANAGER_ATTRIBUTE)
: DEFAULT_CACHE_MANAGER_BEAN_NAME);
}
private static void registerCacheManagerProperty(Element element, BeanDefinition def) {
def.getPropertyValues().add("cacheManager", new RuntimeBeanReference(extractCacheManager(element)));
}
/**
* Registers a
* <pre>
* <bean id="cacheAspect" class="org.springframework.cache.aspectj.AnnotationCacheAspect" factory-method="aspectOf">
* <property name="cacheManagerBeanName" value="cacheManager"/>
* </bean>
*
* </pre>
* @param element
* @param parserContext
*/
private void registerCacheAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ASPECT_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(CACHE_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
registerCacheManagerProperty(element, def);
parserContext.registerBeanComponent(new BeanComponentDefinition(def, CACHE_ASPECT_BEAN_NAME));
}
}
/**
* Inner class to just introduce an AOP framework dependency when actually in proxy mode.
*/
private static class AopAutoProxyConfigurer {
public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
if (!parserContext.getRegistry().containsBeanDefinition(CACHE_ADVISOR_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element);
// Create the CacheDefinitionSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationCacheDefinitionSource.class);
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
// Create the CacheInterceptor definition.
RootBeanDefinition interceptorDef = new RootBeanDefinition(CacheInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerCacheManagerProperty(element, interceptorDef);
interceptorDef.getPropertyValues().add("cacheDefinitionSources", new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// Create the CacheAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryCacheDefinitionSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("cacheDefinitionSource", new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
parserContext.getRegistry().registerBeanDefinition(CACHE_ADVISOR_BEAN_NAME, advisorDef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
eleSource);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, CACHE_ADVISOR_BEAN_NAME));
parserContext.registerComponent(compositeDef);
}
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010 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.config;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* <code>NamespaceHandler</code> allowing for the configuration of
* declarative cache management using either XML or using annotations.
*
* <p>This namespace handler is the central piece of functionality in the
* Spring cache management facilities.
*
* @author Costin Leau
*/
public class CacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2010 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.ehcache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleMapEntry;
import org.springframework.util.Assert;
/**
* {@link Cache} implementation on top of an {@link Ehcache} instance.
*
* @author Costin Leau
*/
public class EhCacheCache implements Cache<Object, Object> {
private final Ehcache cache;
/**
* Creates a {@link EhCacheCache} instance.
*
* @param ehcache backing Ehcache instance
*/
public EhCacheCache(Ehcache ehcache) {
Assert.notNull(ehcache, "non null ehcache required");
Status status = ehcache.getStatus();
Assert.isTrue(Status.STATUS_ALIVE.equals(status), "an 'alive' ehcache is required - current cache is "
+ status.toString());
this.cache = ehcache;
}
public String getName() {
return cache.getName();
}
public Ehcache getNativeCache() {
return cache;
}
public void clear() {
cache.removeAll();
}
public boolean containsKey(Object key) {
return cache.isKeyInCache(key);
}
public boolean containsValue(Object value) {
return cache.isValueInCache(value);
}
@SuppressWarnings("unchecked")
public Set<Map.Entry<Object, Object>> entrySet() {
List<Object> keys = cache.getKeys();
Set<Map.Entry<Object, Object>> entries = new LinkedHashSet<Map.Entry<Object, Object>>(keys.size());
for (Object key : keys) {
Element element = cache.get(key);
if (element != null) {
entries.add(new SimpleMapEntry(key, element.getObjectValue()));
}
}
return Collections.unmodifiableSet(entries);
}
public Object get(Object key) {
Element element = cache.get(key);
return (element != null ? element.getObjectValue() : null);
}
public boolean isEmpty() {
return cache.getSize() == 0;
}
@SuppressWarnings("unchecked")
public Set<Object> keySet() {
List<Object> keys = cache.getKeys();
Set<Object> keySet = new LinkedHashSet<Object>(keys.size());
for (Object key : keys) {
keySet.add(key);
}
return Collections.unmodifiableSet(keySet);
}
public Object put(Object key, Object value) {
Element previous = cache.getQuiet(key);
cache.put(new Element(key, value));
return (previous != null ? previous.getValue() : null);
}
public void putAll(Map<? extends Object, ? extends Object> m) {
for (Map.Entry<? extends Object, ? extends Object> entry : m.entrySet()) {
cache.put(new Element(entry.getKey(), entry.getValue()));
}
}
public Object remove(Object key) {
Object value = null;
if (cache.isKeyInCache(key)) {
Element element = cache.getQuiet(key);
value = (element != null ? element.getObjectValue() : null);
}
cache.remove(key);
return value;
}
public int size() {
return cache.getSize();
}
@SuppressWarnings("unchecked")
public Collection<Object> values() {
List<Object> keys = cache.getKeys();
List<Object> values = new ArrayList<Object>(keys.size());
for (Object key : keys) {
Element element = cache.get(key);
if (element != null) {
values.add(element.getObjectValue());
}
}
return Collections.unmodifiableCollection(values);
}
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;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2010 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.ehcache;
import java.util.Collection;
import java.util.LinkedHashSet;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Status;
import org.springframework.cache.Cache;
import org.springframework.cache.support.AbstractCacheManager;
import org.springframework.util.Assert;
/**
* CacheManager backed by an Ehcache {@link net.sf.ehcache.CacheManager}.
*
* @author Costin Leau
*/
public class EhcacheCacheManager extends AbstractCacheManager {
private net.sf.ehcache.CacheManager cacheManager;
@Override
protected Collection<Cache<?, ?>> loadCaches() {
Assert.notNull(cacheManager, "a backing Ehcache cache manager is required");
Status status = cacheManager.getStatus();
Assert.isTrue(Status.STATUS_ALIVE.equals(status),
"an 'alive' Ehcache cache manager is required - current cache is " + status.toString());
String[] names = cacheManager.getCacheNames();
Collection<Cache<?, ?>> caches = new LinkedHashSet<Cache<?, ?>>(names.length);
for (String name : names) {
caches.add(new EhCacheCache(cacheManager.getEhcache(name)));
}
return caches;
}
@SuppressWarnings("unchecked")
public <K, V> Cache<K, V> getCache(String name) {
Cache cache = super.getCache(name);
if (cache == null) {
// check the Ehcache cache again
// in case the cache was added at runtime
Ehcache ehcache = cacheManager.getEhcache(name);
if (ehcache != null) {
// reinitialize cache map
afterPropertiesSet();
cache = super.getCache(name);
}
}
return cache;
}
/**
* Sets the backing Ehcache {@link net.sf.ehcache.CacheManager}.
*
* @param cacheManager backing Ehcache {@link net.sf.ehcache.CacheManager}
*/
public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2010 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;
/**
* Base class implementing {@link CacheDefinition}.
*
* @author Costin Leau
*/
abstract class AbstractCacheDefinition implements CacheDefinition {
private String cacheName = "";
private String condition = "";
private String key = "";
private String name = "";
public String getCacheName() {
return cacheName;
}
public String getCondition() {
return condition;
}
public String getKey() {
return key;
}
public String getName() {
return name;
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
public void setCondition(String condition) {
this.condition = condition;
}
public void setKey(String key) {
this.key = key;
}
public void setName(String 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(cacheName);
result.append(',');
result.append(condition);
result.append(",");
result.append(key);
return result;
}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2010 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);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2010 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;
}
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2010 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 java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.KeyGenerator;
import org.springframework.cache.support.DefaultKeyGenerator;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Base class for caching aspects, such as the {@link CacheInterceptor}
* or an AspectJ aspect.
*
* <p>This enables the underlying Spring caching infrastructure to be used easily
* to implement an aspect for any aspect system.
*
* <p>Subclasses are responsible for calling methods in this class in the correct order.
*
* <p>If no caching name has been specified in the <code>CacheOperationDefinition</code>,
* the exposed name will be the <code>fully-qualified class name + "." + method name</code>
* (by default).
*
* <p>Uses the <b>Strategy</b> design pattern. A <code>CacheManager</code>
* implementation will perform the actual transaction management, and a
* <code>CacheDefinitionSource</code> is used for determining caching operation definitions.
*
* <p>A cache aspect is serializable if its <code>CacheManager</code>
* and <code>CacheDefinitionSource</code> are serializable.
*
* @author Costin Leau
*/
public abstract class CacheAspectSupport implements InitializingBean {
private static class EmptyHolder implements Serializable {
}
private static final Object NULL_RETURN = new EmptyHolder();
protected final Log logger = LogFactory.getLog(getClass());
private CacheManager cacheManager;
private CacheDefinitionSource cacheDefinitionSource;
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public void afterPropertiesSet() {
if (this.cacheManager == null) {
throw new IllegalStateException("Setting the property 'cacheManager' is required");
}
if (this.cacheDefinitionSource == null) {
throw new IllegalStateException("Either 'cacheDefinitionSource' or 'cacheDefinitionSources' is required: "
+ "If there are no cacheable methods, then don't use a cache aspect.");
}
}
/**
* Convenience method to return a String representation of this Method
* for use in logging. Can be overridden in subclasses to provide a
* different identifier for the given method.
* @param method the method we're interested in
* @param targetClass class the method is on
* @return log message identifying this method
* @see org.springframework.util.ClassUtils#getQualifiedMethodName
*/
protected String methodIdentification(Method method, Class<?> targetClass) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
return ClassUtils.getQualifiedMethodName(specificMethod);
}
public CacheManager getCacheManager() {
return cacheManager;
}
public void setCacheManager(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
public CacheDefinitionSource getCacheDefinitionSource() {
return cacheDefinitionSource;
}
/**
* 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) {
Assert.notEmpty(cacheDefinitionSources);
this.cacheDefinitionSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheDefinitionSource(
cacheDefinitionSources) : cacheDefinitionSources[0]);
}
protected <K, V> Cache<K, V> getCache(CacheDefinition definition) {
// TODO: add support for multiple caches
// TODO: add behaviour for the default cache
String name = definition.getCacheName();
if (!StringUtils.hasText(name)) {
name = cacheManager.getCacheNames().iterator().next();
}
return cacheManager.getCache(name);
}
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 {
// get backing class
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
if (targetClass == null && target != null) {
targetClass = target.getClass();
}
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();
if (context.hasConditionPassed()) {
// check operation
if (cacheDef instanceof CacheUpdateDefinition) {
// 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));
}
}
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);
}
}
return retVal;
}
}
return invocation.call();
}
protected class CacheOperationContext {
private CacheDefinition definition;
private final Cache<?, ?> cache;
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();
public CacheOperationContext(CacheDefinition operationDefinition, Method method, Object[] args,
Class<?> targetClass) {
this.definition = operationDefinition;
this.cache = CacheAspectSupport.this.getCache(definition);
this.method = method;
this.args = args;
this.evalContext = evaluator.createEvaluationContext(cache, method, args, targetClass);
}
/**
* Evaluates the definition condition.
*
* @param definition
* @return
*/
protected boolean hasConditionPassed() {
if (StringUtils.hasText(definition.getCondition())) {
return evaluator.condition(definition.getCondition(), method, evalContext);
}
return true;
}
/**
* Computes the key for the given caching definition.
*
* @param definition
* @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() {
if (StringUtils.hasText(definition.getKey())) {
return evaluator.key(definition.getKey(), method, evalContext);
}
return keyGenerator.extract(args);
}
protected Cache<?, ?> getCache() {
return cache;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 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 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 name of the cache against which this operation is performed.
*
* @return name of the cache on which the operation is performed.
*/
String getCacheName();
/**
* 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();
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010 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);
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2010 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();
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010 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;
/**
* Interface describing the root object used during the expression evaluation.
*
* @author Costin Leau
*/
interface CacheExpressionRootObject {
/**
* Returns the name of the method being cached.
*
* @return name of the cached method.
*/
String getMethodName();
/**
* Returns the cache against which the method is executed.
*
* @return current cache
*/
Cache getCache();
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2010 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 java.util.concurrent.Callable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* AOP Alliance MethodInterceptor for declarative cache
* management using the common Spring caching infrastructure
* ({@link org.springframework.cache.Cache}).
*
* <p>Derives from the {@link CacheAspectSupport} class which
* contains the integration with Spring's underlying caching API.
* CacheInterceptor simply calls the relevant superclass methods
* in the correct order.
*
* <p>CacheInterceptors are thread-safe.
*
* @author Costin Leau
*/
@SuppressWarnings("serial")
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@SuppressWarnings("unchecked")
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Callable<Object> aopAllianceInvocation = new Callable<Object>() {
public Object call() throws Exception {
try {
return invocation.proceed();
} catch (Throwable th) {
if (th instanceof Exception) {
throw (Exception) th;
}
throw (Error) th;
}
}
};
return execute(aopAllianceInvocation, invocation.getThis(), method, invocation.getArguments());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2010 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();
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2010 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.Pointcut;
import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean;
/**
* Proxy factory bean for simplified declarative caching handling.
* This is a convenient alternative to a standard AOP
* {@link org.springframework.aop.framework.ProxyFactoryBean}
* with a separate {@link CachingInterceptor} definition.
*
* <p>This class is intended to cover the <i>typical</i> case of declarative
* transaction demarcation: namely, wrapping a singleton target object with a
* caching proxy, proxying all the interfaces that the target implements.
*
* @author Costin Leau
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see CachingInterceptor
*/
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
private final CacheInterceptor cachingInterceptor = new CacheInterceptor();
private Pointcut pointcut;
@Override
protected Object createMainInterceptor() {
return null;
}
/**
* Set the caching attribute source which is used to find the cache operation
* definition.
*
* @param cacheDefinitionSources cache definition sources
*/
public void setCacheDefinitionSources(CacheDefinitionSource... cacheDefinitionSources) {
this.cachingInterceptor.setCacheDefinitionSources(cacheDefinitionSources);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2010 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();
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2010 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;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2010 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;
/**
* Default implementation of expression root object.
*
* @author Costin Leau
*/
public class DefaultCacheExpressionRootObject implements CacheExpressionRootObject {
private final String methodName;
private final Cache cache;
public DefaultCacheExpressionRootObject(Cache cache, String methodName) {
Assert.hasText(methodName, "method name is required");
this.methodName = methodName;
this.cache = cache;
}
public String getMethodName() {
return methodName;
}
public Cache getCache() {
return cache;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2010 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;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2010 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 {
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2010 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.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.cache.Cache;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Utility class handling the SpEL expression parsing.
* Meant to be used as a reusable, thread-safe component.
*
* Performs internal caching for performance reasons.
*
* @author Costin Leau
*/
class ExpressionEvaluator {
private SpelExpressionParser parser = new SpelExpressionParser();
// shared param discoverer since it caches data internally
private ParameterNameDiscoverer paramNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private Map<Method, Expression> conditionCache = new ConcurrentHashMap<Method, Expression>();
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());
StandardEvaluationContext evaluationContext = new LazyParamAwareEvaluationContext(rootObject,
paramNameDiscoverer, method, args, targetClass, targetMethodCache);
return evaluationContext;
}
boolean condition(String conditionExpression, Method method, EvaluationContext evalContext) {
Expression condExp = conditionCache.get(conditionExpression);
if (condExp == null) {
condExp = parser.parseExpression(conditionExpression);
conditionCache.put(method, condExp);
}
return condExp.getValue(evalContext, boolean.class);
}
Object key(String keyExpression, Method method, EvaluationContext evalContext) {
Expression keyExp = keyCache.get(keyExpression);
if (keyExp == null) {
keyExp = parser.parseExpression(keyExpression);
keyCache.put(method, keyExp);
}
return keyExp.getValue(evalContext);
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2010 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.util.Map;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ObjectUtils;
/**
* Evaluation context class that adds a method parameters as SpEL variables,
* in a lazy manner. The lazy nature eliminates unneeded parsing of classes
* byte code for parameter discovery.
*
* To limit the creation of objects, an ugly constructor is used (rather then a
* dedicated 'closure'-like class for deferred execution).
*
* @author Costin Leau
*/
class LazyParamAwareEvaluationContext extends StandardEvaluationContext {
private final ParameterNameDiscoverer paramDiscoverer;
private final Method method;
private final Object[] args;
private Class<?> targetClass;
private Map<Method, Method> methodCache;
private boolean paramLoaded = false;
LazyParamAwareEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
Object[] args, Class<?> targetClass, Map<Method, Method> methodCache) {
this.paramDiscoverer = paramDiscoverer;
this.method = method;
this.args = args;
this.targetClass = targetClass;
this.methodCache = methodCache;
}
/**
* Load the param information only when needed.
*/
@Override
public Object lookupVariable(String name) {
Object variable = super.lookupVariable(name);
if (variable != null) {
return variable;
}
if (!paramLoaded) {
paramLoaded = true;
loadArgsAsVariables();
variable = super.lookupVariable(name);
}
return variable;
}
private void loadArgsAsVariables() {
// shortcut if no args need to be loaded
if (ObjectUtils.isEmpty(args)) {
return;
}
Method targetMethod = methodCache.get(method);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
methodCache.put(method, targetMethod);
}
// save arguments as indexed variables
for (int i = 0; i < args.length; i++) {
super.setVariable("p" + i, args[i]);
}
String[] parameterNames = paramDiscoverer.getParameterNames(targetMethod);
// save parameter names (if discovered)
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
super.setVariable(parameterNames[i], args[i]);
}
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2010 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.support;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
/**
* Abstract base class implementing the common CacheManager methods. Useful for 'static' environments where the
* backing caches do not change.
*
* @author Costin Leau
*/
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
// fast lookup by name map
private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<String, Cache<?, ?>>();
private Collection<String> names;
public void afterPropertiesSet() {
Collection<Cache<?, ?>> cacheSet = loadCaches();
Assert.notEmpty(cacheSet);
caches.clear();
// preserve the initial order of the cache names
Set<String> cacheNames = new LinkedHashSet<String>(cacheSet.size());
for (Cache<?, ?> cache : cacheSet) {
caches.put(cache.getName(), cache);
cacheNames.add(cache.getName());
}
names = Collections.unmodifiableSet(cacheNames);
}
/**
* Loads the caches into the cache manager. Occurs at startup.
* The returned collection should not be null.
*
* @param caches the collection of caches handled by the manager
*/
protected abstract Collection<Cache<?, ?>> loadCaches();
/**
* Returns the internal cache map.
*
* @return internal cache map
*/
protected final ConcurrentMap<String, Cache<?, ?>> getCacheMap() {
return caches;
}
@SuppressWarnings("unchecked")
public <K, V> Cache<K, V> getCache(String name) {
return (Cache<K, V>) caches.get(name);
}
public Collection<String> getCacheNames() {
return names;
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2010 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.support;
import java.util.Map;
import org.springframework.cache.Cache;
import org.springframework.util.Assert;
/**
* Abstract base class delegating most of the {@link Map}-like methods
* to the underlying cache.
*
* @author Costin Leau
*/
public abstract class AbstractDelegatingCache<K, V> implements Cache<K, V> {
private final Map<K, V> delegate;
public <D extends Map<K, V>> AbstractDelegatingCache(D delegate) {
Assert.notNull(delegate);
this.delegate = delegate;
}
public void clear() {
delegate.clear();
}
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
public V get(Object key) {
return delegate.get(key);
}
public V put(K key, V value) {
return delegate.put(key, value);
}
public V remove(Object key) {
return delegate.remove(key);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2010 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.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.util.Assert;
/**
* Composite {@link CacheManager} implementation that iterates
* over a given collection of {@link CacheManager} instances.
*
* @author Costin Leau
*/
public class CompositeCacheManager implements CacheManager {
private CacheManager[] cacheManagers;
public <K, V> Cache<K, V> getCache(String name) {
Cache<K, V> cache = null;
for (CacheManager cacheManager : cacheManagers) {
cache = cacheManager.getCache(name);
if (cache != null) {
return cache;
}
}
return cache;
}
public Collection<String> getCacheNames() {
List<String> names = new ArrayList<String>();
for (CacheManager manager : cacheManagers) {
names.addAll(manager.getCacheNames());
}
return Collections.unmodifiableCollection(names);
}
public void setCacheManagers(CacheManager[] cacheManagers) {
Assert.notEmpty(cacheManagers, "non-null/empty array required");
this.cacheManagers = cacheManagers.clone();
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010 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.support;
import org.springframework.cache.KeyGenerator;
/**
* @author Costin Leau
*/
public class DefaultKeyGenerator implements KeyGenerator<Object> {
public Object extract(Object... params) {
int hashCode = 17;
for (Object object : params) {
hashCode = 31 * hashCode + object.hashCode();
}
return Integer.valueOf(hashCode);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010 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.support;
import java.util.Collection;
import org.springframework.cache.Cache;
/**
* Simple cache manager working against a given collection of caches. Useful for testing or simple
* caching declarations.
*
* @author Costin Leau
*/
public class MapCacheManager extends AbstractCacheManager {
private Collection<Cache<?, ?>> caches;
@Override
protected Collection<Cache<?, ?>> loadCaches() {
return caches;
}
public void setCaches(Collection<Cache<?, ?>> caches) {
this.caches = caches;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2010 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.support;
import java.util.Map;
import java.util.Map.Entry;
/**
* Basic {@link Entry} implementation.
*
* @author Costin Leau
*/
public class SimpleMapEntry<K, V> implements Entry<K, V> {
private K key;
private V value;
public SimpleMapEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleMapEntry(Map.Entry<K, V> e) {
this.key = e.getKey();
this.value = e.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
static boolean eq(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
}