+ 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.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
|
@ -32,7 +34,7 @@ import org.springframework.util.Assert;
|
|||
* Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource}
|
||||
* 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.
|
||||
* This class may also serve as base class for a custom CacheOperationSource.
|
||||
*
|
||||
|
|
@ -83,13 +85,13 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
|||
|
||||
|
||||
@Override
|
||||
protected CacheOperation findCacheOperation(Class<?> clazz) {
|
||||
return determineCacheOperation(clazz);
|
||||
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
|
||||
return determineCacheOperations(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheOperation findCacheOperation(Method method) {
|
||||
return determineCacheOperation(method);
|
||||
protected Collection<CacheOperation> findCacheOperations(Method method) {
|
||||
return determineCacheOperations(method);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,14 +105,19 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
|
|||
* @return CacheOperation the configured caching operation,
|
||||
* 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) {
|
||||
CacheOperation attr = annotationParser.parseCacheAnnotation(ae);
|
||||
if (attr != null) {
|
||||
return attr;
|
||||
Collection<CacheOperation> annOps = annotationParser.parseCacheAnnotations(ae);
|
||||
if (annOps != null) {
|
||||
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() {
|
||||
return this.publicMethodsOnly;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.cache.annotation;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.cache.interceptor.CacheOperation;
|
||||
|
||||
|
|
@ -25,7 +26,7 @@ import org.springframework.cache.interceptor.CacheOperation;
|
|||
* 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}.
|
||||
* {@link Cacheable}, {@link CacheUpdate} or {@link CacheEvict}.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @since 3.1
|
||||
|
|
@ -43,6 +44,5 @@ public interface CacheAnnotationParser {
|
|||
* or <code>null</code> if none was found
|
||||
* @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.
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,18 @@ package org.springframework.cache.annotation;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.cache.interceptor.CacheEvictOperation;
|
||||
import org.springframework.cache.interceptor.CacheOperation;
|
||||
import org.springframework.cache.interceptor.CacheUpdateOperation;
|
||||
import org.springframework.cache.interceptor.CacheableOperation;
|
||||
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 Juergen Hoeller
|
||||
|
|
@ -34,20 +38,38 @@ import org.springframework.core.annotation.AnnotationUtils;
|
|||
@SuppressWarnings("serial")
|
||||
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
|
||||
|
||||
public CacheOperation parseCacheAnnotation(AnnotatedElement ae) {
|
||||
Cacheable update = AnnotationUtils.getAnnotation(ae, Cacheable.class);
|
||||
if (update != null) {
|
||||
return parseCacheableAnnotation(ae, update);
|
||||
public Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae) {
|
||||
Collection<CacheOperation> ops = null;
|
||||
|
||||
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);
|
||||
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) {
|
||||
CacheUpdateOperation cuo = new CacheUpdateOperation();
|
||||
private Collection<CacheOperation> lazyInit(Collection<CacheOperation> ops) {
|
||||
return (ops != null ? ops : new ArrayList<CacheOperation>(2));
|
||||
}
|
||||
|
||||
CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable ann) {
|
||||
CacheableOperation cuo = new CacheableOperation();
|
||||
cuo.setCacheNames(ann.value());
|
||||
cuo.setCondition(ann.condition());
|
||||
cuo.setKey(ann.key());
|
||||
|
|
@ -65,4 +87,40 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
|
|||
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.
|
||||
* Hooked into Spring's caching interception infrastructure
|
||||
* via CacheDefinitionSource implementation.
|
||||
* via CacheOperationSource implementation.
|
||||
*
|
||||
*/
|
||||
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.CacheInterceptor;
|
||||
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.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
|
@ -148,7 +148,7 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
|
|||
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
|
||||
TypedStringValue nameHolder = new TypedStringValue(name);
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package org.springframework.cache.interceptor;
|
|||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
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
|
||||
* 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.
|
||||
|
|
@ -65,7 +67,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
* <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, 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
|
||||
* 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.
|
||||
Object cacheKey = getCacheKey(method, targetClass);
|
||||
CacheOperation cached = this.attributeCache.get(cacheKey);
|
||||
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
|
||||
if (cached != null) {
|
||||
if (cached == NULL_CACHING_ATTRIBUTE) {
|
||||
return null;
|
||||
|
|
@ -90,18 +92,18 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
}
|
||||
else {
|
||||
// We need to work it out.
|
||||
CacheOperation cacheDef = computeCacheOperationDefinition(method, targetClass);
|
||||
Collection<CacheOperation> cacheDefs = computeCacheOperationDefinition(method, targetClass);
|
||||
// Put it in the cache.
|
||||
if (cacheDef == null) {
|
||||
if (cacheDefs == null) {
|
||||
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
|
||||
}
|
||||
else {
|
||||
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);
|
||||
}
|
||||
|
||||
private CacheOperation computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
||||
private Collection<CacheOperation> computeCacheOperationDefinition(Method method, Class<?> targetClass) {
|
||||
// Don't allow no-public methods as required.
|
||||
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
|
||||
return null;
|
||||
|
|
@ -130,25 +132,25 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
|
||||
// First try is the method in the target class.
|
||||
CacheOperation opDef = findCacheOperation(specificMethod);
|
||||
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
|
||||
if (opDef != null) {
|
||||
return opDef;
|
||||
}
|
||||
|
||||
// Second try is the caching operation on the target class.
|
||||
opDef = findCacheOperation(specificMethod.getDeclaringClass());
|
||||
opDef = findCacheOperations(specificMethod.getDeclaringClass());
|
||||
if (opDef != null) {
|
||||
return opDef;
|
||||
}
|
||||
|
||||
if (specificMethod != method) {
|
||||
// Fall back is to look at the original method.
|
||||
opDef = findCacheOperation(method);
|
||||
opDef = findCacheOperations(method);
|
||||
if (opDef != null) {
|
||||
return opDef;
|
||||
}
|
||||
// Last fall back is the class of the original method.
|
||||
return findCacheOperation(method.getDeclaringClass());
|
||||
return findCacheOperations(method.getDeclaringClass());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -161,7 +163,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
* @return all caching attribute associated with this method
|
||||
* (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
|
||||
|
|
@ -170,7 +172,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
* @return all caching attribute associated with this class
|
||||
* (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?
|
||||
|
|
@ -213,5 +215,4 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
|
|||
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.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
|
@ -31,6 +32,7 @@ import org.springframework.cache.CacheManager;
|
|||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +49,7 @@ import org.springframework.util.StringUtils;
|
|||
* <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.
|
||||
* and <code>CacheOperationSource</code> are serializable.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Juergen Hoeller
|
||||
|
|
@ -71,6 +73,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||
|
||||
private boolean initialized = false;
|
||||
|
||||
private static final String CACHEABLE = "cacheable", UPDATE = "cacheupdate", EVICT = "cacheevict";
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Assert.notEmpty(cacheDefinitionSources);
|
||||
this.cacheOperationSource = (cacheDefinitionSources.length > 1 ?
|
||||
new CompositeCacheOperationSource(cacheDefinitionSources) : cacheDefinitionSources[0]);
|
||||
this.cacheOperationSource = (cacheDefinitionSources.length > 1 ? new CompositeCacheOperationSource(
|
||||
cacheDefinitionSources) : cacheDefinitionSources[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -138,7 +141,6 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to return a String representation of this Method
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
protected Collection<Cache> getCaches(CacheOperation operation) {
|
||||
Set<String> cacheNames = operation.getCacheNames();
|
||||
Collection<Cache> caches = new ArrayList<Cache>(cacheNames.size());
|
||||
|
|
@ -180,107 +181,239 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||
return invoker.invoke();
|
||||
}
|
||||
|
||||
boolean log = logger.isTraceEnabled();
|
||||
|
||||
// get backing class
|
||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
||||
if (targetClass == null && target != null) {
|
||||
targetClass = target.getClass();
|
||||
}
|
||||
final CacheOperation cacheOp = getCacheOperationSource().getCacheOperation(method, targetClass);
|
||||
|
||||
Object retVal = null;
|
||||
final Collection<CacheOperation> cacheOp = getCacheOperationSource().getCacheOperations(method, targetClass);
|
||||
|
||||
// analyze caching information
|
||||
if (cacheOp != null) {
|
||||
CacheOperationContext context = getOperationContext(cacheOp, method, args, target, targetClass);
|
||||
Collection<Cache> caches = context.getCaches();
|
||||
if (!CollectionUtils.isEmpty(cacheOp)) {
|
||||
Map<String, Collection<CacheOperationContext>> ops = createOperationContext(cacheOp, method, args, target,
|
||||
targetClass);
|
||||
|
||||
if (context.hasConditionPassed()) {
|
||||
// check operation
|
||||
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);
|
||||
}
|
||||
// start with evictions
|
||||
inspectCacheEvicts(ops.get(EVICT));
|
||||
|
||||
// for each cache
|
||||
boolean cacheHit = false;
|
||||
// follow up with cacheable
|
||||
CacheStatus status = inspectCacheables(ops.get(CACHEABLE));
|
||||
|
||||
for (Iterator<Cache> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
|
||||
Cache cache = iterator.next();
|
||||
Cache.ValueWrapper wrapper = cache.get(key);
|
||||
if (wrapper != null) {
|
||||
cacheHit = true;
|
||||
retVal = wrapper.get();
|
||||
}
|
||||
}
|
||||
Object retVal = null;
|
||||
Map<CacheOperationContext, Object> updates = inspectCacheUpdates(ops.get(UPDATE));
|
||||
|
||||
if (!cacheHit) {
|
||||
if (log) {
|
||||
logger.trace("Key " + key + " NOT found in cache(s), invoking cached target method "
|
||||
+ method);
|
||||
}
|
||||
retVal = invoker.invoke();
|
||||
// update all caches
|
||||
for (Cache cache : caches) {
|
||||
cache.put(key, retVal);
|
||||
}
|
||||
if (status != null) {
|
||||
if (status.updateRequired) {
|
||||
updates.putAll(status.cUpdates);
|
||||
}
|
||||
// return cached object
|
||||
else {
|
||||
if (log) {
|
||||
logger.trace("Key " + key + " found in cache, returning value " + retVal);
|
||||
}
|
||||
return status.retVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheOp instanceof CacheEvictOperation) {
|
||||
CacheEvictOperation evictOp = (CacheEvictOperation) cacheOp;
|
||||
|
||||
// for each cache
|
||||
// lazy key initialization
|
||||
Object key = null;
|
||||
|
||||
for (Cache cache : caches) {
|
||||
// flush the cache (ignore arguments)
|
||||
if (evictOp.isCacheWide()) {
|
||||
cache.clear();
|
||||
if (log) {
|
||||
logger.trace("Invalidating entire cache for definition " + cacheOp +
|
||||
" on method " + method);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// check key
|
||||
if (key == null) {
|
||||
key = context.generateKey();
|
||||
}
|
||||
if (log) {
|
||||
logger.trace("Invalidating cache key " + key + " for definition " + cacheOp
|
||||
+ " on method " + method);
|
||||
}
|
||||
cache.evict(key);
|
||||
}
|
||||
}
|
||||
retVal = invoker.invoke();
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
update(updates, retVal);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
else {
|
||||
if (log) {
|
||||
logger.trace("Cache condition failed on method " + method + " for definition " + cacheOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// lazy key initialization
|
||||
Object key = null;
|
||||
|
||||
for (Cache cache : context.getCaches()) {
|
||||
// cache-wide flush
|
||||
if (evictOp.isCacheWide()) {
|
||||
cache.clear();
|
||||
if (log) {
|
||||
logger.trace("Invalidating entire cache for definition " + evictOp + " on method " + context.method);
|
||||
}
|
||||
} else {
|
||||
// check key
|
||||
if (key == null) {
|
||||
key = context.generateKey();
|
||||
}
|
||||
if (log) {
|
||||
logger.trace("Invalidating cache key " + key + " for definition " + evictOp + " on method " + context.method);
|
||||
}
|
||||
cache.evict(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (log) {
|
||||
logger.trace("Cache condition failed on method " + context.method + " for definition " + context.operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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 {
|
||||
|
||||
|
|
@ -308,7 +441,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||
this.evalContext = evaluator.createEvaluationContext(caches, method, args, target, targetClass);
|
||||
}
|
||||
|
||||
protected boolean hasConditionPassed() {
|
||||
protected boolean isConditionPassing() {
|
||||
if (StringUtils.hasText(this.operation.getCondition())) {
|
||||
return evaluator.condition(this.operation.getCondition(), this.method, this.evalContext);
|
||||
}
|
||||
|
|
@ -330,5 +463,4 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
|||
return this.caches;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ public class CacheOperationEditor extends PropertyEditorSupport {
|
|||
CacheOperation op;
|
||||
|
||||
if ("cacheable".contains(tokens[0])) {
|
||||
op = new CacheUpdateOperation();
|
||||
op = new CacheableOperation();
|
||||
}
|
||||
|
||||
else if ("evict".contains(tokens[0])) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.cache.interceptor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Interface used by CacheInterceptor. Implementations know
|
||||
|
|
@ -30,13 +31,13 @@ public interface CacheOperationSource {
|
|||
|
||||
/**
|
||||
* 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 targetClass the target class (may be <code>null</code>,
|
||||
* in which case the declaring class of the method must be used)
|
||||
* @return {@link CacheOperation} the matching cache operation,
|
||||
* 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 org.springframework.aop.support.StaticMethodMatcherPointcut;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -34,7 +35,7 @@ abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut
|
|||
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
CacheOperationSource cas = getCacheOperationSource();
|
||||
return (cas == null || cas.getCacheOperation(method, targetClass) != null);
|
||||
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
|
||||
}
|
||||
|
||||
@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");
|
||||
* 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.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
|
@ -33,7 +35,6 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
|||
|
||||
private final CacheOperationSource[] cacheOperationSources;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new CompositeCacheOperationSource for the given sources.
|
||||
* @param cacheOperationSources the CacheOperationSource instances to combine
|
||||
|
|
@ -43,7 +44,6 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
|||
this.cacheOperationSources = cacheOperationSources;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the CacheOperationSource instances that this CompositeCachingDefinitionSource combines.
|
||||
*/
|
||||
|
|
@ -51,15 +51,19 @@ public class CompositeCacheOperationSource implements CacheOperationSource, Seri
|
|||
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) {
|
||||
CacheOperation definition = source.getCacheOperation(method, targetClass);
|
||||
if (definition != null) {
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
Collection<CacheOperation> cacheOperations = source.getCacheOperations(method, targetClass);
|
||||
if (cacheOperations != null) {
|
||||
if (ops == null) {
|
||||
ops = new ArrayList<CacheOperation>();
|
||||
}
|
||||
|
||||
ops.addAll(cacheOperations);
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package org.springframework.cache.interceptor;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -43,7 +45,7 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
|||
protected static final Log logger = LogFactory.getLog(NameMatchCacheOperationSource.class);
|
||||
|
||||
/** 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
|
||||
|
|
@ -88,13 +90,13 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
|||
if (logger.isDebugEnabled()) {
|
||||
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
|
||||
String methodName = method.getName();
|
||||
CacheOperation attr = this.nameMap.get(methodName);
|
||||
Collection<CacheOperation> attr = this.nameMap.get(methodName);
|
||||
|
||||
if (attr == null) {
|
||||
// Look for most specific name match.
|
||||
|
|
|
|||
|
|
@ -176,6 +176,31 @@ public abstract class AbstractAnnotationTests {
|
|||
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
|
||||
public void testCacheable() throws Exception {
|
||||
testCacheable(cs);
|
||||
|
|
@ -284,4 +309,24 @@ public abstract class AbstractAnnotationTests {
|
|||
public void testClassUncheckedException() throws Exception {
|
||||
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 org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CacheUpdate;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
|
||||
/**
|
||||
|
|
@ -61,6 +62,16 @@ public class AnnotatedClassCacheableService implements CacheableService {
|
|||
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) {
|
||||
nullInvocations.incrementAndGet();
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ public interface CacheableService<T> {
|
|||
|
||||
T nullValue(Object arg1);
|
||||
|
||||
T update(Object arg1);
|
||||
|
||||
T conditionalUpdate(Object arg2);
|
||||
|
||||
Number nullInvocations();
|
||||
|
||||
T rootVars(Object arg1);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.springframework.cache.config;
|
|||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CacheUpdate;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
|
||||
/**
|
||||
|
|
@ -64,6 +65,16 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
|||
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")
|
||||
public Long nullValue(Object arg1) {
|
||||
nullInvocations.incrementAndGet();
|
||||
|
|
|
|||
Loading…
Reference in New Issue