+ introduced @CacheUpdate annotation
+ introduced @CacheDefinition annotation + introduced meta-annotation to allow multiple @Cache annotations SPR-7833 SPR-8082
This commit is contained in:
parent
c3f0f31243
commit
eddb0ac3be
|
|
@ -19,6 +19,8 @@ package org.springframework.cache.annotation;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -32,7 +34,7 @@ import org.springframework.util.Assert;
|
||||||
* Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource}
|
* Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource}
|
||||||
* interface for working with caching metadata in JDK 1.5+ annotation format.
|
* 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}
|
* <p>This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CacheUpdate} and {@link CacheEvict}
|
||||||
* annotations and exposes corresponding caching operation definition to Spring's cache infrastructure.
|
* annotations and exposes corresponding caching operation definition to Spring's cache infrastructure.
|
||||||
* This class may also serve as base class for a custom CacheOperationSource.
|
* This class may also serve as base class for a custom CacheOperationSource.
|
||||||
*
|
*
|
||||||
|
|
@ -83,13 +85,13 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CacheOperation findCacheOperation(Class<?> clazz) {
|
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
|
||||||
return determineCacheOperation(clazz);
|
return determineCacheOperations(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CacheOperation findCacheOperation(Method method) {
|
protected Collection<CacheOperation> findCacheOperations(Method method) {
|
||||||
return determineCacheOperation(method);
|
return determineCacheOperations(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,14 +105,19 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||||
* @return CacheOperation the configured caching operation,
|
* @return CacheOperation the configured caching operation,
|
||||||
* or <code>null</code> if none was found
|
* or <code>null</code> if none was found
|
||||||
*/
|
*/
|
||||||
protected CacheOperation determineCacheOperation(AnnotatedElement ae) {
|
protected Collection<CacheOperation> determineCacheOperations(AnnotatedElement ae) {
|
||||||
|
Collection<CacheOperation> ops = null;
|
||||||
|
|
||||||
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
|
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
|
||||||
CacheOperation attr = annotationParser.parseCacheAnnotation(ae);
|
Collection<CacheOperation> annOps = annotationParser.parseCacheAnnotations(ae);
|
||||||
if (attr != null) {
|
if (annOps != null) {
|
||||||
return attr;
|
if (ops == null) {
|
||||||
|
ops = new ArrayList<CacheOperation>();
|
||||||
|
}
|
||||||
|
ops.addAll(annOps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -120,5 +127,4 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
||||||
protected boolean allowPublicMethodsOnly() {
|
protected boolean allowPublicMethodsOnly() {
|
||||||
return this.publicMethodsOnly;
|
return this.publicMethodsOnly;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.cache.annotation;
|
package org.springframework.cache.annotation;
|
||||||
|
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.springframework.cache.interceptor.CacheOperation;
|
import org.springframework.cache.interceptor.CacheOperation;
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ import org.springframework.cache.interceptor.CacheOperation;
|
||||||
* Strategy interface for parsing known caching annotation types.
|
* Strategy interface for parsing known caching annotation types.
|
||||||
* {@link AnnotationCacheDefinitionSource} delegates to such
|
* {@link AnnotationCacheDefinitionSource} delegates to such
|
||||||
* parsers for supporting specific annotation types such as Spring's own
|
* parsers for supporting specific annotation types such as Spring's own
|
||||||
* {@link Cacheable} or {@link CacheEvict}.
|
* {@link Cacheable}, {@link CacheUpdate} or {@link CacheEvict}.
|
||||||
*
|
*
|
||||||
* @author Costin Leau
|
* @author Costin Leau
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
|
|
@ -43,6 +44,5 @@ public interface CacheAnnotationParser {
|
||||||
* or <code>null</code> if none was found
|
* or <code>null</code> if none was found
|
||||||
* @see AnnotationCacheOperationSource#determineCacheOperation
|
* @see AnnotationCacheOperationSource#determineCacheOperation
|
||||||
*/
|
*/
|
||||||
CacheOperation parseCacheAnnotation(AnnotatedElement ae);
|
Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2011 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.annotation;
|
||||||
|
|
||||||
|
import java.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group annotation for multiple cacheable annotations (of different or the same type).
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
public @interface CacheDefinition {
|
||||||
|
|
||||||
|
Cacheable[] cacheables();
|
||||||
|
|
||||||
|
CacheUpdate[] updates();
|
||||||
|
|
||||||
|
CacheEvict[] evicts();
|
||||||
|
}
|
||||||
60
org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java
vendored
Normal file
60
org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2011 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.annotation;
|
||||||
|
|
||||||
|
import java.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 update operation. As opposed to {@link Cacheable} annotation, this annotation
|
||||||
|
* does not cause the target method to be skipped in case of a cache hit - rather it
|
||||||
|
* always causes the method to be invoked and its result to be placed into the cache.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
public @interface CacheUpdate {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the caches in which the update takes place.
|
||||||
|
* <p>May be used to determine the target cache (or caches), matching the
|
||||||
|
* qualifier value (or the bean name(s)) of (a) specific bean definition.
|
||||||
|
*/
|
||||||
|
String[] value();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 cache update.
|
||||||
|
* <p>Default is "", meaning the method result is always cached.
|
||||||
|
*/
|
||||||
|
String condition() default "";
|
||||||
|
}
|
||||||
|
|
@ -56,5 +56,4 @@ public @interface Cacheable {
|
||||||
* <p>Default is "", meaning the method is always cached.
|
* <p>Default is "", meaning the method is always cached.
|
||||||
*/
|
*/
|
||||||
String condition() default "";
|
String condition() default "";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,18 @@ package org.springframework.cache.annotation;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.springframework.cache.interceptor.CacheEvictOperation;
|
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||||
import org.springframework.cache.interceptor.CacheOperation;
|
import org.springframework.cache.interceptor.CacheOperation;
|
||||||
import org.springframework.cache.interceptor.CacheUpdateOperation;
|
import org.springframework.cache.interceptor.CacheUpdateOperation;
|
||||||
|
import org.springframework.cache.interceptor.CacheableOperation;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strategy implementation for parsing Spring's {@link Cacheable} and {@link CacheEvict} annotations.
|
* Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CacheUpdate} annotations.
|
||||||
*
|
*
|
||||||
* @author Costin Leau
|
* @author Costin Leau
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
|
@ -34,20 +38,38 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
|
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
|
||||||
|
|
||||||
public CacheOperation parseCacheAnnotation(AnnotatedElement ae) {
|
public Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae) {
|
||||||
Cacheable update = AnnotationUtils.getAnnotation(ae, Cacheable.class);
|
Collection<CacheOperation> ops = null;
|
||||||
if (update != null) {
|
|
||||||
return parseCacheableAnnotation(ae, update);
|
Cacheable cache = AnnotationUtils.getAnnotation(ae, Cacheable.class);
|
||||||
|
if (cache != null) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
ops.add(parseCacheableAnnotation(ae, cache));
|
||||||
}
|
}
|
||||||
CacheEvict evict = AnnotationUtils.getAnnotation(ae, CacheEvict.class);
|
CacheEvict evict = AnnotationUtils.getAnnotation(ae, CacheEvict.class);
|
||||||
if (evict != null) {
|
if (evict != null) {
|
||||||
return parseEvictAnnotation(ae, evict);
|
ops = lazyInit(ops);
|
||||||
|
ops.add(parseEvictAnnotation(ae, evict));
|
||||||
}
|
}
|
||||||
return null;
|
CacheUpdate update = AnnotationUtils.getAnnotation(ae, CacheUpdate.class);
|
||||||
|
if (update != null) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
ops.add(parseUpdateAnnotation(ae, update));
|
||||||
|
}
|
||||||
|
CacheDefinition definition = AnnotationUtils.getAnnotation(ae, CacheDefinition.class);
|
||||||
|
if (definition != null) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
ops.addAll(parseDefinitionAnnotation(ae, definition));
|
||||||
|
}
|
||||||
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheUpdateOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable ann) {
|
private Collection<CacheOperation> lazyInit(Collection<CacheOperation> ops) {
|
||||||
CacheUpdateOperation cuo = new CacheUpdateOperation();
|
return (ops != null ? ops : new ArrayList<CacheOperation>(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable ann) {
|
||||||
|
CacheableOperation cuo = new CacheableOperation();
|
||||||
cuo.setCacheNames(ann.value());
|
cuo.setCacheNames(ann.value());
|
||||||
cuo.setCondition(ann.condition());
|
cuo.setCondition(ann.condition());
|
||||||
cuo.setKey(ann.key());
|
cuo.setKey(ann.key());
|
||||||
|
|
@ -65,4 +87,40 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
|
||||||
return ceo;
|
return ceo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CacheUpdate ann) {
|
||||||
|
CacheUpdateOperation cuo = new CacheUpdateOperation();
|
||||||
|
cuo.setCacheNames(ann.value());
|
||||||
|
cuo.setCondition(ann.condition());
|
||||||
|
cuo.setKey(ann.key());
|
||||||
|
cuo.setName(ae.toString());
|
||||||
|
return cuo;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<CacheOperation> parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinition ann) {
|
||||||
|
Collection<CacheOperation> ops = null;
|
||||||
|
|
||||||
|
Cacheable[] cacheables = ann.cacheables();
|
||||||
|
if (!ObjectUtils.isEmpty(cacheables)) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
for (Cacheable cacheable : cacheables) {
|
||||||
|
ops.add(parseCacheableAnnotation(ae, cacheable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CacheEvict[] evicts = ann.evicts();
|
||||||
|
if (!ObjectUtils.isEmpty(evicts)) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
for (CacheEvict evict : evicts) {
|
||||||
|
ops.add(parseEvictAnnotation(ae, evict));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CacheUpdate[] updates = ann.updates();
|
||||||
|
if (!ObjectUtils.isEmpty(updates)) {
|
||||||
|
ops = lazyInit(ops);
|
||||||
|
for (CacheUpdate update : updates) {
|
||||||
|
ops.add(parseUpdateAnnotation(ae, update));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* JDK 1.5+ annotation for caching demarcation.
|
* JDK 1.5+ annotation for caching demarcation.
|
||||||
* Hooked into Spring's caching interception infrastructure
|
* Hooked into Spring's caching interception infrastructure
|
||||||
* via CacheDefinitionSource implementation.
|
* via CacheOperationSource implementation.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package org.springframework.cache.annotation;
|
package org.springframework.cache.annotation;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import org.springframework.cache.annotation.AnnotationCacheOperationSource;
|
||||||
import org.springframework.cache.interceptor.CacheEvictOperation;
|
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||||
import org.springframework.cache.interceptor.CacheInterceptor;
|
import org.springframework.cache.interceptor.CacheInterceptor;
|
||||||
import org.springframework.cache.interceptor.CacheOperation;
|
import org.springframework.cache.interceptor.CacheOperation;
|
||||||
import org.springframework.cache.interceptor.CacheUpdateOperation;
|
import org.springframework.cache.interceptor.CacheableOperation;
|
||||||
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
|
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.util.xml.DomUtils;
|
import org.springframework.util.xml.DomUtils;
|
||||||
|
|
@ -148,7 +148,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
||||||
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
|
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
|
||||||
TypedStringValue nameHolder = new TypedStringValue(name);
|
TypedStringValue nameHolder = new TypedStringValue(name);
|
||||||
nameHolder.setSource(parserContext.extractSource(opElement));
|
nameHolder.setSource(parserContext.extractSource(opElement));
|
||||||
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheUpdateOperation());
|
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
|
||||||
|
|
||||||
cacheOpeMap.put(nameHolder, op);
|
cacheOpeMap.put(nameHolder, op);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
|
@ -51,7 +53,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
* Canonical value held in cache to indicate no caching attribute was
|
* Canonical value held in cache to indicate no caching attribute was
|
||||||
* found for this method and we don't need to look again.
|
* found for this method and we don't need to look again.
|
||||||
*/
|
*/
|
||||||
private final static CacheOperation NULL_CACHING_ATTRIBUTE = new CacheUpdateOperation();
|
private final static Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger available to subclasses.
|
* Logger available to subclasses.
|
||||||
|
|
@ -65,7 +67,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
* <p>As this base class is not marked Serializable, the cache will be recreated
|
* <p>As this base class is not marked Serializable, the cache will be recreated
|
||||||
* after serialization - provided that the concrete subclass is Serializable.
|
* after serialization - provided that the concrete subclass is Serializable.
|
||||||
*/
|
*/
|
||||||
final Map<Object, CacheOperation> attributeCache = new ConcurrentHashMap<Object, CacheOperation>();
|
final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<Object, Collection<CacheOperation>>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -76,10 +78,10 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
* @return {@link CacheOperation} for this method, or <code>null</code> if the method
|
* @return {@link CacheOperation} for this method, or <code>null</code> if the method
|
||||||
* is not cacheable
|
* is not cacheable
|
||||||
*/
|
*/
|
||||||
public CacheOperation getCacheOperation(Method method, Class<?> targetClass) {
|
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
|
||||||
// First, see if we have a cached value.
|
// First, see if we have a cached value.
|
||||||
Object cacheKey = getCacheKey(method, targetClass);
|
Object cacheKey = getCacheKey(method, targetClass);
|
||||||
CacheOperation cached = this.attributeCache.get(cacheKey);
|
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
if (cached == NULL_CACHING_ATTRIBUTE) {
|
if (cached == NULL_CACHING_ATTRIBUTE) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -90,18 +92,18 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We need to work it out.
|
// We need to work it out.
|
||||||
CacheOperation cacheDef = computeCacheOperationDefinition(method, targetClass);
|
Collection<CacheOperation> cacheDefs = computeCacheOperationDefinition(method, targetClass);
|
||||||
// Put it in the cache.
|
// Put it in the cache.
|
||||||
if (cacheDef == null) {
|
if (cacheDefs == null) {
|
||||||
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
|
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheDef);
|
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheDefs);
|
||||||
}
|
}
|
||||||
this.attributeCache.put(cacheKey, cacheDef);
|
this.attributeCache.put(cacheKey, cacheDefs);
|
||||||
}
|
}
|
||||||
return cacheDef;
|
return cacheDefs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,7 +119,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
return new DefaultCacheKey(method, targetClass);
|
return new DefaultCacheKey(method, targetClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CacheOperation computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
private Collection<CacheOperation> computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
||||||
// Don't allow no-public methods as required.
|
// Don't allow no-public methods as required.
|
||||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -130,25 +132,25 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||||
|
|
||||||
// First try is the method in the target class.
|
// First try is the method in the target class.
|
||||||
CacheOperation opDef = findCacheOperation(specificMethod);
|
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
|
||||||
if (opDef != null) {
|
if (opDef != null) {
|
||||||
return opDef;
|
return opDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second try is the caching operation on the target class.
|
// Second try is the caching operation on the target class.
|
||||||
opDef = findCacheOperation(specificMethod.getDeclaringClass());
|
opDef = findCacheOperations(specificMethod.getDeclaringClass());
|
||||||
if (opDef != null) {
|
if (opDef != null) {
|
||||||
return opDef;
|
return opDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specificMethod != method) {
|
if (specificMethod != method) {
|
||||||
// Fall back is to look at the original method.
|
// Fall back is to look at the original method.
|
||||||
opDef = findCacheOperation(method);
|
opDef = findCacheOperations(method);
|
||||||
if (opDef != null) {
|
if (opDef != null) {
|
||||||
return opDef;
|
return opDef;
|
||||||
}
|
}
|
||||||
// Last fall back is the class of the original method.
|
// Last fall back is the class of the original method.
|
||||||
return findCacheOperation(method.getDeclaringClass());
|
return findCacheOperations(method.getDeclaringClass());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +163,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
* @return all caching attribute associated with this method
|
* @return all caching attribute associated with this method
|
||||||
* (or <code>null</code> if none)
|
* (or <code>null</code> if none)
|
||||||
*/
|
*/
|
||||||
protected abstract CacheOperation findCacheOperation(Method method);
|
protected abstract Collection<CacheOperation> findCacheOperations(Method method);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses need to implement this to return the caching attribute
|
* Subclasses need to implement this to return the caching attribute
|
||||||
|
|
@ -170,7 +172,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
* @return all caching attribute associated with this class
|
* @return all caching attribute associated with this class
|
||||||
* (or <code>null</code> if none)
|
* (or <code>null</code> if none)
|
||||||
*/
|
*/
|
||||||
protected abstract CacheOperation findCacheOperation(Class<?> clazz);
|
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should only public methods be allowed to have caching semantics?
|
* Should only public methods be allowed to have caching semantics?
|
||||||
|
|
@ -213,5 +215,4 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
||||||
return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
|
return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
@ -19,7 +19,8 @@ package org.springframework.cache.interceptor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
@ -31,6 +32,7 @@ import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.expression.EvaluationContext;
|
import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,7 +49,7 @@ import org.springframework.util.StringUtils;
|
||||||
* <code>CacheDefinitionSource</code> is used for determining caching operation definitions.
|
* <code>CacheDefinitionSource</code> is used for determining caching operation definitions.
|
||||||
*
|
*
|
||||||
* <p>A cache aspect is serializable if its <code>CacheManager</code>
|
* <p>A cache aspect is serializable if its <code>CacheManager</code>
|
||||||
* and <code>CacheDefinitionSource</code> are serializable.
|
* and <code>CacheOperationSource</code> are serializable.
|
||||||
*
|
*
|
||||||
* @author Costin Leau
|
* @author Costin Leau
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
|
@ -71,6 +73,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
|
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
|
|
||||||
|
private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the CacheManager that this cache aspect should delegate to.
|
* Set the CacheManager that this cache aspect should delegate to.
|
||||||
|
|
@ -92,8 +95,8 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
*/
|
*/
|
||||||
public void setCacheOperationSources(CacheOperationSource... cacheDefinitionSources) {
|
public void setCacheOperationSources(CacheOperationSource... cacheDefinitionSources) {
|
||||||
Assert.notEmpty(cacheDefinitionSources);
|
Assert.notEmpty(cacheDefinitionSources);
|
||||||
this.cacheOperationSource = (cacheDefinitionSources.length > 1 ?
|
this.cacheOperationSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheOperationSource(
|
||||||
new CompositeCacheOperationSource(cacheDefinitionSources) : cacheDefinitionSources[0]);
|
cacheDefinitionSources) : cacheDefinitionSources[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,7 +141,6 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to return a String representation of this Method
|
* Convenience method to return a String representation of this Method
|
||||||
* for use in logging. Can be overridden in subclasses to provide a
|
* for use in logging. Can be overridden in subclasses to provide a
|
||||||
|
|
@ -153,7 +155,6 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
return ClassUtils.getQualifiedMethodName(specificMethod);
|
return ClassUtils.getQualifiedMethodName(specificMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Collection<Cache> getCaches(CacheOperation operation) {
|
protected Collection<Cache> getCaches(CacheOperation operation) {
|
||||||
Set<String> cacheNames = operation.getCacheNames();
|
Set<String> cacheNames = operation.getCacheNames();
|
||||||
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
||||||
|
|
@ -180,107 +181,239 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
return invoker.invoke();
|
return invoker.invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean log = logger.isTraceEnabled();
|
|
||||||
|
|
||||||
// get backing class
|
// get backing class
|
||||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
||||||
if (targetClass == null && target != null) {
|
if (targetClass == null && target != null) {
|
||||||
targetClass = target.getClass();
|
targetClass = target.getClass();
|
||||||
}
|
}
|
||||||
final CacheOperation cacheOp = getCacheOperationSource().getCacheOperation(method, targetClass);
|
final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);
|
||||||
|
|
||||||
Object retVal = null;
|
|
||||||
|
|
||||||
// analyze caching information
|
// analyze caching information
|
||||||
if (cacheOp != null) {
|
if (!CollectionUtils.isEmpty(cacheOp)) {
|
||||||
CacheOperationContext context = getOperationContext(cacheOp, method, args, target, targetClass);
|
Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target,
|
||||||
Collection<Cache> caches = context.getCaches();
|
targetClass);
|
||||||
|
|
||||||
if (context.hasConditionPassed()) {
|
// start with evictions
|
||||||
// check operation
|
inspectCacheEvicts(ops.get(EVICT));
|
||||||
if (cacheOp instanceof CacheUpdateOperation) {
|
|
||||||
Object key = context.generateKey();
|
|
||||||
if (log) {
|
|
||||||
logger.trace("Computed cache key " + key + " for definition " + cacheOp);
|
|
||||||
}
|
|
||||||
if (key == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Null key returned for cache definition (maybe you are using named params on classes without debug info?) "
|
|
||||||
+ cacheOp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each cache
|
// follow up with cacheable
|
||||||
boolean cacheHit = false;
|
CacheStatus status = inspectCacheables(ops.get(CACHEABLE));
|
||||||
|
|
||||||
for (Iterator<Cache> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
|
Object retVal = null;
|
||||||
Cache cache = iterator.next();
|
Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE));
|
||||||
Cache.ValueWrapper wrapper = cache.get(key);
|
|
||||||
if (wrapper != null) {
|
|
||||||
cacheHit = true;
|
|
||||||
retVal = wrapper.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cacheHit) {
|
if (status != null) {
|
||||||
if (log) {
|
if (status.updateRequired) {
|
||||||
logger.trace("Key " + key + " NOT found in cache(s), invoking cached target method "
|
updates.putAll(status.cUpdates);
|
||||||
+ method);
|
|
||||||
}
|
|
||||||
retVal = invoker.invoke();
|
|
||||||
// update all caches
|
|
||||||
for (Cache cache : caches) {
|
|
||||||
cache.put(key, retVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (log) {
|
|
||||||
logger.trace("Key " + key + " found in cache, returning value " + retVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// return cached object
|
||||||
|
else {
|
||||||
|
return status.retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cacheOp instanceof CacheEvictOperation) {
|
retVal = invoker.invoke();
|
||||||
CacheEvictOperation evictOp = (CacheEvictOperation) cacheOp;
|
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
update(updates, retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoker.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inspectCacheEvicts(Collection<CacheOperationContext> evictions) {
|
||||||
|
|
||||||
|
if (!evictions.isEmpty()) {
|
||||||
|
|
||||||
|
boolean log = logger.isTraceEnabled();
|
||||||
|
|
||||||
|
for (CacheOperationContext context : evictions) {
|
||||||
|
if (context.isConditionPassing()) {
|
||||||
|
CacheEvictOperation evictOp = (CacheEvictOperation) context.operation;
|
||||||
|
|
||||||
// for each cache
|
// for each cache
|
||||||
// lazy key initialization
|
// lazy key initialization
|
||||||
Object key = null;
|
Object key = null;
|
||||||
|
|
||||||
for (Cache cache : caches) {
|
for (Cache cache : context.getCaches()) {
|
||||||
// flush the cache (ignore arguments)
|
// cache-wide flush
|
||||||
if (evictOp.isCacheWide()) {
|
if (evictOp.isCacheWide()) {
|
||||||
cache.clear();
|
cache.clear();
|
||||||
if (log) {
|
if (log) {
|
||||||
logger.trace("Invalidating entire cache for definition " + cacheOp +
|
logger.trace("Invalidating entire cache for definition " + evictOp + " on method " + context.method);
|
||||||
" on method " + method);
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// check key
|
// check key
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
key = context.generateKey();
|
key = context.generateKey();
|
||||||
}
|
}
|
||||||
if (log) {
|
if (log) {
|
||||||
logger.trace("Invalidating cache key " + key + " for definition " + cacheOp
|
logger.trace("Invalidating cache key " + key + " for definition " + evictOp + " on method " + context.method);
|
||||||
+ " on method " + method);
|
|
||||||
}
|
}
|
||||||
cache.evict(key);
|
cache.evict(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retVal = invoker.invoke();
|
|
||||||
}
|
}
|
||||||
return retVal;
|
else {
|
||||||
|
if (log) {
|
||||||
|
logger.trace("Cache condition failed on method " + context.method + " for definition " + context.operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
if (log) {
|
}
|
||||||
logger.trace("Cache condition failed on method " + method + " for definition " + cacheOp);
|
|
||||||
|
private CacheStatus inspectCacheables(Collection<CacheOperationContext> cacheables) {
|
||||||
|
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(
|
||||||
|
cacheables.size());
|
||||||
|
|
||||||
|
boolean updateRequire = false;
|
||||||
|
Object retVal = null;
|
||||||
|
|
||||||
|
if (!cacheables.isEmpty()) {
|
||||||
|
boolean log = logger.isTraceEnabled();
|
||||||
|
boolean atLeastOnePassed = false;
|
||||||
|
|
||||||
|
for (CacheOperationContext context : cacheables) {
|
||||||
|
if (context.isConditionPassing()) {
|
||||||
|
atLeastOnePassed = true;
|
||||||
|
Object key = context.generateKey();
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
logger.trace("Computed cache key " + key + " for definition " + context.operation);
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Null key returned for cache definition (maybe you are using named params on classes without debug info?) "
|
||||||
|
+ context.operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add op/key (in case an update is discovered later on)
|
||||||
|
cUpdates.put(context, key);
|
||||||
|
|
||||||
|
boolean localCacheHit = false;
|
||||||
|
|
||||||
|
// check whether the cache needs to be inspected or not (the method will be invoked anyway)
|
||||||
|
if (!updateRequire) {
|
||||||
|
for (Cache cache : context.getCaches()) {
|
||||||
|
Cache.ValueWrapper wrapper = cache.get(key);
|
||||||
|
if (wrapper != null) {
|
||||||
|
retVal = wrapper.get();
|
||||||
|
localCacheHit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localCacheHit) {
|
||||||
|
updateRequire = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (log) {
|
||||||
|
logger.trace("Cache condition failed on method " + context.method + " for definition " + context.operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a status only if at least on cacheable matched
|
||||||
|
if (atLeastOnePassed) {
|
||||||
|
return new CacheStatus(cUpdates, updateRequire, retVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CacheStatus {
|
||||||
|
// caches/key
|
||||||
|
final Map<CacheOperationContext, Object> cUpdates;
|
||||||
|
final boolean updateRequired;
|
||||||
|
final Object retVal;
|
||||||
|
|
||||||
|
CacheStatus(Map<CacheOperationContext, Object> cUpdates, boolean updateRequired, Object retVal) {
|
||||||
|
this.cUpdates = cUpdates;
|
||||||
|
this.updateRequired = updateRequired;
|
||||||
|
this.retVal = retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<CacheOperationContext, Object> inspectCacheUpdates(Collection<CacheOperationContext> updates) {
|
||||||
|
|
||||||
|
Map<CacheOperationContext, Object> cUpdates = new LinkedHashMap<CacheOperationContext, Object>(updates.size());
|
||||||
|
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
boolean log = logger.isTraceEnabled();
|
||||||
|
|
||||||
|
for (CacheOperationContext context : updates) {
|
||||||
|
if (context.isConditionPassing()) {
|
||||||
|
|
||||||
|
Object key = context.generateKey();
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
logger.trace("Computed cache key " + key + " for definition " + context.operation);
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Null key returned for cache definition (maybe you are using named params on classes without debug info?) "
|
||||||
|
+ context.operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add op/key (in case an update is discovered later on)
|
||||||
|
cUpdates.put(context, key);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (log) {
|
||||||
|
logger.trace("Cache condition failed on method " + context.method + " for definition " + context.operation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return invoker.invoke();
|
return cUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void update(Map<CacheOperationContext, Object> updates, Object retVal) {
|
||||||
|
for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) {
|
||||||
|
for (Cache cache : entry.getKey().getCaches()) {
|
||||||
|
cache.put(entry.getValue(), retVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Collection<CacheOperationContext>> createOperationContext(Collection<CacheOperation> cacheOp,
|
||||||
|
Method method, Object[] args, Object target, Class<?> targetClass) {
|
||||||
|
Map<String, Collection<CacheOperationContext>> map = new LinkedHashMap<String, Collection<CacheOperationContext>>(3);
|
||||||
|
|
||||||
|
Collection<CacheOperationContext> cacheables = new ArrayList<CacheOperationContext>();
|
||||||
|
Collection<CacheOperationContext> evicts = new ArrayList<CacheOperationContext>();
|
||||||
|
Collection<CacheOperationContext> updates = new ArrayList<CacheOperationContext>();
|
||||||
|
|
||||||
|
for (CacheOperation cacheOperation : cacheOp) {
|
||||||
|
CacheOperationContext opContext = getOperationContext(cacheOperation, method, args, target, targetClass);
|
||||||
|
|
||||||
|
if (cacheOperation instanceof CacheableOperation) {
|
||||||
|
cacheables.add(opContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheOperation instanceof CacheEvictOperation) {
|
||||||
|
evicts.add(opContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheOperation instanceof CacheUpdateOperation) {
|
||||||
|
updates.add(opContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.put(CACHEABLE, cacheables);
|
||||||
|
map.put(EVICT, evicts);
|
||||||
|
map.put(UPDATE, updates);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
protected class CacheOperationContext {
|
protected class CacheOperationContext {
|
||||||
|
|
||||||
|
|
@ -308,7 +441,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass);
|
this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasConditionPassed() {
|
protected boolean isConditionPassing() {
|
||||||
if (StringUtils.hasText(this.operation.getCondition())) {
|
if (StringUtils.hasText(this.operation.getCondition())) {
|
||||||
return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext);
|
return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext);
|
||||||
}
|
}
|
||||||
|
|
@ -330,5 +463,4 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
return this.caches;
|
return this.caches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
@ -57,7 +57,7 @@ public class CacheOperationEditor extends PropertyEditorSupport {
|
||||||
CacheOperation op;
|
CacheOperation op;
|
||||||
|
|
||||||
if ("cacheable".contains(tokens[0])) {
|
if ("cacheable".contains(tokens[0])) {
|
||||||
op = new CacheUpdateOperation();
|
op = new CacheableOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ("evict".contains(tokens[0])) {
|
else if ("evict".contains(tokens[0])) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.cache.interceptor;
|
package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface used by CacheInterceptor. Implementations know
|
* Interface used by CacheInterceptor. Implementations know
|
||||||
|
|
@ -30,13 +31,13 @@ public interface CacheOperationSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the cache operation definition for this method,
|
* Return the cache operation definition for this method,
|
||||||
* or <code>null</code> if the method is not cacheable.
|
* or <code>null</code> if the method contains no "cacheable" annotations.
|
||||||
* @param method the method to introspect
|
* @param method the method to introspect
|
||||||
* @param targetClass the target class (may be <code>null</code>,
|
* @param targetClass the target class (may be <code>null</code>,
|
||||||
* in which case the declaring class of the method must be used)
|
* in which case the declaring class of the method must be used)
|
||||||
* @return {@link CacheOperation} the matching cache operation,
|
* @return {@link CacheOperation} the matching cache operation,
|
||||||
* or <code>null</code> if none found
|
* or <code>null</code> if none found
|
||||||
*/
|
*/
|
||||||
CacheOperation getCacheOperation(Method method, Class<?> targetClass);
|
Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.springframework.aop.support.StaticMethodMatcherPointcut;
|
import org.springframework.aop.support.StaticMethodMatcherPointcut;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,7 +35,7 @@ abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut
|
||||||
|
|
||||||
public boolean matches(Method method, Class<?> targetClass) {
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
CacheOperationSource cas = getCacheOperationSource();
|
CacheOperationSource cas = getCacheOperationSource();
|
||||||
return (cas == null || cas.getCacheOperation(method, targetClass) != null);
|
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2011 the original author or authors.
|
* Copyright 2011 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2011 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class describing a cache 'cacheable' operation.
|
||||||
|
*
|
||||||
|
* @author Costin Leau
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public class CacheableOperation extends CacheOperation {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
@ -33,7 +35,6 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
||||||
|
|
||||||
private final CacheOperationSource[] cacheOperationSources;
|
private final CacheOperationSource[] cacheOperationSources;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new CompositeCacheOperationSource for the given sources.
|
* Create a new CompositeCacheOperationSource for the given sources.
|
||||||
* @param cacheOperationSources the CacheOperationSource instances to combine
|
* @param cacheOperationSources the CacheOperationSource instances to combine
|
||||||
|
|
@ -43,7 +44,6 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
||||||
this.cacheOperationSources = cacheOperationSources;
|
this.cacheOperationSources = cacheOperationSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the CacheOperationSource instances that this CompositeCachingDefinitionSource combines.
|
* Return the CacheOperationSource instances that this CompositeCachingDefinitionSource combines.
|
||||||
*/
|
*/
|
||||||
|
|
@ -51,15 +51,19 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
||||||
return this.cacheOperationSources;
|
return this.cacheOperationSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
|
||||||
|
Collection<CacheOperation> ops = null;
|
||||||
|
|
||||||
public CacheOperation getCacheOperation(Method method, Class<?> targetClass) {
|
|
||||||
for (CacheOperationSource source : this.cacheOperationSources) {
|
for (CacheOperationSource source : this.cacheOperationSources) {
|
||||||
CacheOperation definition = source.getCacheOperation(method, targetClass);
|
Collection<CacheOperation> cacheOperations = source.getCacheOperations(method, targetClass);
|
||||||
if (definition != null) {
|
if (cacheOperations != null) {
|
||||||
return definition;
|
if (ops == null) {
|
||||||
|
ops = new ArrayList<CacheOperation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ops.addAll(cacheOperations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package org.springframework.cache.interceptor;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -43,7 +45,7 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||||
protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
|
protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
|
||||||
|
|
||||||
/** Keys are method names; values are TransactionAttributes */
|
/** Keys are method names; values are TransactionAttributes */
|
||||||
private Map<String, CacheOperation> nameMap = new LinkedHashMap<String, CacheOperation>();
|
private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<String, Collection<CacheOperation>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a name/attribute map, consisting of method names
|
* Set a name/attribute map, consisting of method names
|
||||||
|
|
@ -88,13 +90,13 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Adding method [" + methodName + "] with cache operation [" + operation + "]");
|
logger.debug("Adding method [" + methodName + "] with cache operation [" + operation + "]");
|
||||||
}
|
}
|
||||||
this.nameMap.put(methodName, operation);
|
this.nameMap.put(methodName, Collections.singleton(operation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public CacheOperation getCacheOperation(Method method, Class<?> targetClass) {
|
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
|
||||||
// look for direct name match
|
// look for direct name match
|
||||||
String methodName = method.getName();
|
String methodName = method.getName();
|
||||||
CacheOperation attr = this.nameMap.get(methodName);
|
Collection<CacheOperation> attr = this.nameMap.get(methodName);
|
||||||
|
|
||||||
if (attr == null) {
|
if (attr == null) {
|
||||||
// Look for most specific name match.
|
// Look for most specific name match.
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,31 @@ public abstract class AbstractAnnotationTests {
|
||||||
assertSame(r1, service.cache(null));
|
assertSame(r1, service.cache(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCacheUpdate(CacheableService service) {
|
||||||
|
Object o = new Object();
|
||||||
|
Cache cache = cm.getCache("default");
|
||||||
|
assertNull(cache.get(o));
|
||||||
|
Object r1 = service.update(o);
|
||||||
|
assertSame(r1, cache.get(o).get());
|
||||||
|
|
||||||
|
o = new Object();
|
||||||
|
assertNull(cache.get(o));
|
||||||
|
Object r2 = service.update(o);
|
||||||
|
assertSame(r2, cache.get(o).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConditionalCacheUpdate(CacheableService service) {
|
||||||
|
Integer one = Integer.valueOf(1);
|
||||||
|
Integer three = Integer.valueOf(3);
|
||||||
|
|
||||||
|
Cache cache = cm.getCache("default");
|
||||||
|
assertEquals(one, Integer.valueOf(service.conditionalUpdate(one).toString()));
|
||||||
|
assertNull(cache.get(one));
|
||||||
|
|
||||||
|
assertEquals(three, Integer.valueOf(service.conditionalUpdate(three).toString()));
|
||||||
|
assertEquals(three, Integer.valueOf(cache.get(three).get().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCacheable() throws Exception {
|
public void testCacheable() throws Exception {
|
||||||
testCacheable(cs);
|
testCacheable(cs);
|
||||||
|
|
@ -284,4 +309,24 @@ public abstract class AbstractAnnotationTests {
|
||||||
public void testClassUncheckedException() throws Exception {
|
public void testClassUncheckedException() throws Exception {
|
||||||
testUncheckedThrowable(ccs);
|
testUncheckedThrowable(ccs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdate() {
|
||||||
|
testCacheUpdate(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassUpdate() {
|
||||||
|
testCacheUpdate(ccs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConditionalUpdate() {
|
||||||
|
testConditionalCacheUpdate(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassConditionalUpdate() {
|
||||||
|
testConditionalCacheUpdate(ccs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.cache.config;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.cache.annotation.CacheUpdate;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,6 +62,16 @@ public class AnnotatedClassCacheableService implements CacheableService {
|
||||||
return counter.getAndIncrement();
|
return counter.getAndIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheUpdate("default")
|
||||||
|
public Object update(Object arg1) {
|
||||||
|
return counter.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheUpdate(value = "default", condition = "#arg.equals(3)")
|
||||||
|
public Object conditionalUpdate(Object arg) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
|
||||||
public Object nullValue(Object arg1) {
|
public Object nullValue(Object arg1) {
|
||||||
nullInvocations.incrementAndGet();
|
nullInvocations.incrementAndGet();
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ public interface CacheableService<T> {
|
||||||
|
|
||||||
T nullValue(Object arg1);
|
T nullValue(Object arg1);
|
||||||
|
|
||||||
|
T update(Object arg1);
|
||||||
|
|
||||||
|
T conditionalUpdate(Object arg2);
|
||||||
|
|
||||||
Number nullInvocations();
|
Number nullInvocations();
|
||||||
|
|
||||||
T rootVars(Object arg1);
|
T rootVars(Object arg1);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package org.springframework.cache.config;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
|
import org.springframework.cache.annotation.CacheUpdate;
|
||||||
import org.springframework.cache.annotation.Cacheable;
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,6 +65,16 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||||
return counter.getAndIncrement();
|
return counter.getAndIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CacheUpdate("default")
|
||||||
|
public Long update(Object arg1) {
|
||||||
|
return counter.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
@CacheUpdate(value = "default", condition = "#arg.equals(3)")
|
||||||
|
public Long conditionalUpdate(Object arg) {
|
||||||
|
return Long.valueOf(arg.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Cacheable("default")
|
@Cacheable("default")
|
||||||
public Long nullValue(Object arg1) {
|
public Long nullValue(Object arg1) {
|
||||||
nullInvocations.incrementAndGet();
|
nullInvocations.incrementAndGet();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue