SPR-8082
SPR-7833
+ add support for CacheDefinitions declarations inside XML
+ more integration tests
This commit is contained in:
Costin Leau 2011-11-09 17:53:51 +00:00
parent e4c88553d8
commit dc88a7c8ba
20 changed files with 497 additions and 175 deletions

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.springframework.org/schema/cache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tool="http://www.springframework.org/schema/tool"
targetNamespace="http://www.springframework.org/schema/cache"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"/>
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-3.0.xsd"/>
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the elements used in the Spring Framework's declarative
cache management infrastructure.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="annotation-driven">
<xsd:complexType>
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.annotation.AnnotationCacheOperationDefinitionSource"><![CDATA[
Indicates that cache configuration is defined by Java 5
annotations on bean classes, and that proxies are automatically
to be created for the relevant annotated beans.
The default annotations supported are Spring's @Cacheable and @CacheEvict.
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
The bean name of the CacheManager that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the bean name of the desired CacheManager
is not 'cacheManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.CacheManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="key-generator" type="xsd:string">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
This attribute is not required, and only needs to be specified
explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="mode" default="proxy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Should annotated beans be proxied using Spring's AOP framework,
or should they rather be weaved with an AspectJ transaction aspect?
AspectJ weaving requires spring-aspects.jar on the classpath,
as well as load-time weaving (or compile-time weaving) enabled.
Note: The weaving-based aspect requires the @Cacheable and @CacheInvalidate
annotations to be defined on the concrete class. Annotations in interfaces
will not work in that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="proxy"/>
<xsd:enumeration value="aspectj"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Are class-based (CGLIB) proxies to be created? By default, standard
Java interface-based proxies are created.
Note: Class-based proxies require the @Cacheable and @CacheInvalidate annotations
to be defined on the concrete class. Annotations in interfaces will not work
in that case (they will rather only work with interface-based proxies)!
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="order" type="xsd:int">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
Controls the ordering of the execution of the cache advisor
when multiple advice executes at a specific joinpoint.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@ -34,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}, {@link CacheUpdate} and {@link CacheEvict}
* <p>This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CachePut} 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.
*

View File

@ -26,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}, {@link CacheUpdate} or {@link CacheEvict}.
* {@link Cacheable}, {@link CachePut} or {@link CacheEvict}.
*
* @author Costin Leau
* @since 3.1

View File

@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Group annotation for multiple cacheable annotations (of different or the same type).
* Group annotation for multiple cache annotations (of different or the same type).
*
* @author Costin Leau
* @since 3.1
@ -33,11 +33,11 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheDefinition {
public @interface CacheDefinitions {
Cacheable[] cacheables();
Cacheable[] cacheable() default {};
CacheUpdate[] updates();
CachePut[] put() default {};
CacheEvict[] evicts();
CacheEvict[] evict() default {};
}

View File

@ -23,11 +23,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.cache.Cache;
/**
*
* 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
* a {@link Cache#put(Object, Object)} operation. As opposed to {@link Cacheable} annotation,
* this annotation does not cause the target method to be skipped - rather it
* always causes the method to be invoked and its result to be placed into the cache.
*
* @author Costin Leau
@ -37,7 +39,7 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheUpdate {
public @interface CachePut {
/**
* Name of the caches in which the update takes place.

View File

@ -23,13 +23,13 @@ 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.CachePutOperation;
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}, {@link CacheEvict} and {@link CacheUpdate} annotations.
* Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CachePut} annotations.
*
* @author Costin Leau
* @author Juergen Hoeller
@ -51,12 +51,12 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
ops = lazyInit(ops);
ops.add(parseEvictAnnotation(ae, evict));
}
CacheUpdate update = AnnotationUtils.getAnnotation(ae, CacheUpdate.class);
CachePut update = AnnotationUtils.getAnnotation(ae, CachePut.class);
if (update != null) {
ops = lazyInit(ops);
ops.add(parseUpdateAnnotation(ae, update));
}
CacheDefinition definition = AnnotationUtils.getAnnotation(ae, CacheDefinition.class);
CacheDefinitions definition = AnnotationUtils.getAnnotation(ae, CacheDefinitions.class);
if (definition != null) {
ops = lazyInit(ops);
ops.addAll(parseDefinitionAnnotation(ae, definition));
@ -87,8 +87,8 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return ceo;
}
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CacheUpdate ann) {
CacheUpdateOperation cuo = new CacheUpdateOperation();
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut ann) {
CachePutOperation cuo = new CachePutOperation();
cuo.setCacheNames(ann.value());
cuo.setCondition(ann.condition());
cuo.setKey(ann.key());
@ -96,27 +96,27 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
return cuo;
}
Collection<CacheOperation> parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinition ann) {
Collection<CacheOperation> parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinitions ann) {
Collection<CacheOperation> ops = null;
Cacheable[] cacheables = ann.cacheables();
Cacheable[] cacheables = ann.cacheable();
if (!ObjectUtils.isEmpty(cacheables)) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cacheable));
}
}
CacheEvict[] evicts = ann.evicts();
CacheEvict[] evicts = ann.evict();
if (!ObjectUtils.isEmpty(evicts)) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, evict));
}
}
CacheUpdate[] updates = ann.updates();
CachePut[] updates = ann.put();
if (!ObjectUtils.isEmpty(updates)) {
ops = lazyInit(ops);
for (CacheUpdate update : updates) {
for (CachePut update : updates) {
ops.add(parseUpdateAnnotation(ae, update));
}
}

View File

@ -16,6 +16,8 @@
package org.springframework.cache.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.config.TypedStringValue;
@ -30,6 +32,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.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
import org.springframework.util.StringUtils;
@ -52,13 +55,14 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
*/
private static class Props {
private String key, condition;
private String key, condition, method;
private String[] caches = null;
Props(Element root) {
String defaultCache = root.getAttribute("cache");
key = root.getAttribute("key");
condition = root.getAttribute("condition");
method = root.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(defaultCache)) {
caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
@ -95,10 +99,24 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
return op;
}
String merge(Element element, ReaderContext readerCtx) {
String m = element.getAttribute(METHOD_ATTRIBUTE);
if (StringUtils.hasText(m)) {
return m.trim();
}
if (StringUtils.hasText(method)) {
return method;
}
readerCtx.error("No method specified for " + element.getNodeName(), element);
return null;
}
}
private static final String CACHEABLE_ELEMENT = "cacheable";
private static final String CACHE_EVICT_ELEMENT = "cache-evict";
private static final String CACHE_PUT_ELEMENT = "cache-put";
private static final String METHOD_ATTRIBUTE = "method";
private static final String DEFS_ELEMENT = "definitions";
@ -139,34 +157,60 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
Props prop = new Props(definition);
// add cacheable first
ManagedMap<TypedStringValue, CacheOperation> cacheOpeMap = new ManagedMap<TypedStringValue, CacheOperation>();
cacheOpeMap.setSource(parserContext.extractSource(definition));
ManagedMap<TypedStringValue, Collection<CacheOperation>> cacheOpMap = new ManagedMap<TypedStringValue, Collection<CacheOperation>>();
cacheOpMap.setSource(parserContext.extractSource(definition));
List<Element> updateCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
List<Element> cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
for (Element opElement : updateCacheMethods) {
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
for (Element opElement : cacheableCacheMethods) {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
cacheOpeMap.put(nameHolder, op);
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
}
List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
for (Element opElement : evictCacheMethods) {
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
cacheOpeMap.put(nameHolder, op);
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
}
List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
for (Element opElement : putCacheMethods) {
String name = prop.merge(opElement, parserContext.getReaderContext());
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(opElement));
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
if (col == null) {
col = new ArrayList<CacheOperation>(2);
cacheOpMap.put(nameHolder, col);
}
col.add(op);
}
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(definition));
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpeMap);
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
return attributeSourceDefinition;
}
}

View File

@ -403,7 +403,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
evicts.add(opContext);
}
if (cacheOperation instanceof CacheUpdateOperation) {
if (cacheOperation instanceof CachePutOperation) {
updates.add(opContext);
}
}

View File

@ -1,84 +0,0 @@
/*
* 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.interceptor;
import java.beans.PropertyEditorSupport;
import org.springframework.util.StringUtils;
/**
* PropertyEditor for {@link CacheOperation} objects. Accepts a String of form
* <p><tt>action,cache,key,condition</tt>
* <p>where only action and cache are required. Available definitions for action are
* <tt>cacheable</tt> and <tt>evict</tt>.
* When specifying multiple caches, use ; as a separator
*
* A typical example would be:
* <p><code>cacheable, orders;books, #p0</code>
*
* <p>The tokens need to be specified in the order above.
*
* @author Costin Leau
*
* @see org.springframework.transaction.TransactionAttributeEditor
* @see org.springframework.core.Constants
*/
public class CacheOperationEditor extends PropertyEditorSupport {
/**
* Format is action, cache, key, condition.
* Null or the empty string means that the method is non cacheable.
* @see java.beans.PropertyEditor#setAsText(java.lang.String)
*/
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasLength(text)) {
// tokenize it with ","
String[] tokens = StringUtils.commaDelimitedListToStringArray(text);
if (tokens.length < 2) {
throw new IllegalArgumentException(
"too little arguments found, at least the cache action and cache name are required");
}
CacheOperation op;
if ("cacheable".contains(tokens[0])) {
op = new CacheableOperation();
}
else if ("evict".contains(tokens[0])) {
op = new CacheEvictOperation();
} else {
throw new IllegalArgumentException("Invalid cache action specified " + tokens[0]);
}
op.setCacheNames(StringUtils.delimitedListToStringArray(tokens[1], ";"));
if (tokens.length > 2) {
op.setKey(tokens[2]);
}
if (tokens.length > 3) {
op.setCondition(tokens[3]);
}
setValue(op);
} else {
setValue(null);
}
}
}

View File

@ -17,11 +17,11 @@
package org.springframework.cache.interceptor;
/**
* Class describing a cache 'update' operation.
* Class describing a cache 'put' operation.
*
* @author Costin Leau
* @since 3.1
*/
public class CacheUpdateOperation extends CacheOperation {
public class CachePutOperation extends CacheOperation {
}

View File

@ -19,11 +19,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;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -54,63 +51,44 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
* @see CacheOperation
* @see CacheOperationEditor
*/
public void setNameMap(Map<String, CacheOperation> nameMap) {
for (Map.Entry<String, CacheOperation> entry : nameMap.entrySet()) {
public void setNameMap(Map<String, Collection<CacheOperation>> nameMap) {
for (Map.Entry<String, Collection<CacheOperation>> entry : nameMap.entrySet()) {
addCacheMethod(entry.getKey(), entry.getValue());
}
}
/**
* Parses the given properties into a name/attribute map.
* Expects method names as keys and String attributes definitions as values,
* parsable into CacheOperation instances via CacheOperationEditor.
* @see #setNameMap
* @see CacheOperationEditor
*/
public void setProperties(Properties cacheOperations) {
CacheOperationEditor tae = new CacheOperationEditor();
Enumeration propNames = cacheOperations.propertyNames();
while (propNames.hasMoreElements()) {
String methodName = (String) propNames.nextElement();
String value = cacheOperations.getProperty(methodName);
tae.setAsText(value);
CacheOperation op = (CacheOperation) tae.getValue();
addCacheMethod(methodName, op);
}
}
/**
* Add an attribute for a cacheable method.
* <p>Method names can be exact matches, or of the pattern "xxx*",
* "*xxx" or "*xxx*" for matching multiple methods.
* @param methodName the name of the method
* @param operation operation associated with the method
* @param ops operation associated with the method
*/
public void addCacheMethod(String methodName, CacheOperation operation) {
public void addCacheMethod(String methodName, Collection<CacheOperation> ops) {
if (logger.isDebugEnabled()) {
logger.debug("Adding method [" + methodName + "] with cache operation [" + operation + "]");
logger.debug("Adding method [" + methodName + "] with cache operations [" + ops + "]");
}
this.nameMap.put(methodName, Collections.singleton(operation));
this.nameMap.put(methodName, ops);
}
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
// look for direct name match
String methodName = method.getName();
Collection<CacheOperation> attr = this.nameMap.get(methodName);
Collection<CacheOperation> ops = this.nameMap.get(methodName);
if (attr == null) {
if (ops == null) {
// Look for most specific name match.
String bestNameMatch = null;
for (String mappedName : this.nameMap.keySet()) {
if (isMatch(methodName, mappedName)
&& (bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
attr = this.nameMap.get(mappedName);
ops = this.nameMap.get(mappedName);
bestNameMatch = mappedName;
}
}
}
return attr;
return ops;
}
/**

View File

@ -182,35 +182,29 @@
The SpEL expression used for conditioning the method caching.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="definitionType">
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:attribute name="method" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
<xsd:attribute name="method" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
The method name(s) with which the cache attributes are to be
associated. The wildcard (*) character can be used to associate the
same cache attribute settings with a number of methods; for
example, 'get*', 'handle*', '*Order', 'on*Event', etc.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
example, 'get*', 'handle*', '*Order', 'on*Event', etc.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="definitionsType">
<xsd:complexContent>
<xsd:extension base="basedefinitionType">
<xsd:sequence>
<xsd:choice>
<xsd:element name="cacheable" minOccurs="0" maxOccurs="unbounded" type="definitionType"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="cacheable" minOccurs="0" maxOccurs="unbounded" type="basedefinitionType"/>
<xsd:element name="cache-put" minOccurs="0" maxOccurs="unbounded" type="basedefinitionType"/>
<xsd:element name="cache-evict" minOccurs="0" maxOccurs="unbounded">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="definitionType">
<xsd:extension base="basedefinitionType">
<xsd:attribute name="all-entries" type="xsd:boolean" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[

View File

@ -18,6 +18,7 @@ package org.springframework.cache.config;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.UUID;
import org.junit.Before;
@ -51,6 +52,10 @@ public abstract class AbstractAnnotationTests {
cs = ctx.getBean("service", CacheableService.class);
ccs = ctx.getBean("classService", CacheableService.class);
cm = ctx.getBean(CacheManager.class);
Collection<String> cn = cm.getCacheNames();
assertTrue(cn.contains("default"));
assertTrue(cn.contains("secondary"));
assertTrue(cn.contains("primary"));
}
public void testCacheable(CacheableService service) throws Exception {
@ -201,6 +206,124 @@ public abstract class AbstractAnnotationTests {
assertEquals(three, Integer.valueOf(cache.get(three).get().toString()));
}
public void testMultiCache(CacheableService service) {
Object o1 = new Object();
Object o2 = new Object();
Cache primary = cm.getCache("primary");
Cache secondary = cm.getCache("secondary");
assertNull(primary.get(o1));
assertNull(secondary.get(o1));
Object r1 = service.multiCache(o1);
assertSame(r1, primary.get(o1).get());
assertSame(r1, secondary.get(o1).get());
Object r2 = service.multiCache(o1);
Object r3 = service.multiCache(o1);
assertSame(r1, r2);
assertSame(r1, r3);
assertNull(primary.get(o2));
assertNull(secondary.get(o2));
Object r4 = service.multiCache(o2);
assertSame(r4, primary.get(o2).get());
assertSame(r4, secondary.get(o2).get());
}
public void testMultiEvict(CacheableService service) {
Object o1 = new Object();
Object r1 = service.multiCache(o1);
Object r2 = service.multiCache(o1);
Cache primary = cm.getCache("primary");
Cache secondary = cm.getCache("secondary");
assertSame(r1, r2);
assertSame(r1, primary.get(o1).get());
assertSame(r1, secondary.get(o1).get());
service.multiEvict(o1);
assertNull(primary.get(o1));
assertNull(secondary.get(o1));
Object r3 = service.multiCache(o1);
Object r4 = service.multiCache(o1);
assertNotSame(r1, r3);
assertSame(r3, r4);
assertSame(r3, primary.get(o1).get());
assertSame(r4, secondary.get(o1).get());
}
public void testMultiPut(CacheableService service) {
Object o = Integer.valueOf(1);
Cache primary = cm.getCache("primary");
Cache secondary = cm.getCache("secondary");
assertNull(primary.get(o));
assertNull(secondary.get(o));
Object r1 = service.multiUpdate(o);
assertSame(r1, primary.get(o).get());
assertSame(r1, secondary.get(o).get());
o = Integer.valueOf(2);
assertNull(primary.get(o));
assertNull(secondary.get(o));
Object r2 = service.multiUpdate(o);
assertSame(r2, primary.get(o).get());
assertSame(r2, secondary.get(o).get());
}
public void testMultiCacheAndEvict(CacheableService service) {
String methodName = "multiCacheAndEvict";
Cache primary = cm.getCache("primary");
Cache secondary = cm.getCache("secondary");
Object key = Integer.valueOf(1);
secondary.put(key, key);
assertNull(secondary.get(methodName));
assertSame(key, secondary.get(key).get());
Object r1 = service.multiCacheAndEvict(key);
assertSame(r1, service.multiCacheAndEvict(key));
// assert the method name is used
assertSame(r1, primary.get(methodName).get());
assertNull(secondary.get(methodName));
assertNull(secondary.get(key));
}
public void testMultiConditionalCacheAndEvict(CacheableService service) {
Cache primary = cm.getCache("primary");
Cache secondary = cm.getCache("secondary");
Object key = Integer.valueOf(1);
secondary.put(key, key);
assertNull(primary.get(key));
assertSame(key, secondary.get(key).get());
Object r1 = service.multiConditionalCacheAndEvict(key);
Object r3 = service.multiConditionalCacheAndEvict(key);
assertTrue(!r1.equals(r3));
assertNull(primary.get(key));
Object key2 = Integer.valueOf(3);
Object r2 = service.multiConditionalCacheAndEvict(key2);
assertSame(r2, service.multiConditionalCacheAndEvict(key2));
// assert the method name is used
assertSame(r2, primary.get(key2).get());
assertNull(secondary.get(key2));
}
@Test
public void testCacheable() throws Exception {
testCacheable(cs);
@ -329,4 +452,54 @@ public abstract class AbstractAnnotationTests {
public void testClassConditionalUpdate() {
testConditionalCacheUpdate(ccs);
}
@Test
public void testMultiCache() {
testMultiCache(cs);
}
@Test
public void testClassMultiCache() {
testMultiCache(ccs);
}
@Test
public void testMultiEvict() {
testMultiEvict(cs);
}
@Test
public void testClassMultiEvict() {
testMultiEvict(ccs);
}
@Test
public void testMultiPut() {
testMultiPut(cs);
}
@Test
public void testClassMultiPut() {
testMultiPut(ccs);
}
@Test
public void testMultiCacheAndEvict() {
testMultiCacheAndEvict(cs);
}
@Test
public void testClassMultiCacheAndEvict() {
testMultiCacheAndEvict(ccs);
}
@Test
public void testMultiConditionalCacheAndEvict() {
testMultiConditionalCacheAndEvict(cs);
}
@Test
public void testClassMultiConditionalCacheAndEvict() {
testMultiConditionalCacheAndEvict(ccs);
}
}

View File

@ -18,8 +18,9 @@ package org.springframework.cache.config;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.cache.annotation.CacheDefinitions;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CacheUpdate;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
/**
@ -62,12 +63,12 @@ public class AnnotatedClassCacheableService implements CacheableService {
return counter.getAndIncrement();
}
@CacheUpdate("default")
@CachePut("default")
public Object update(Object arg1) {
return counter.getAndIncrement();
}
@CacheUpdate(value = "default", condition = "#arg.equals(3)")
@CachePut(value = "default", condition = "#arg.equals(3)")
public Object conditionalUpdate(Object arg) {
return arg;
}
@ -88,4 +89,31 @@ public class AnnotatedClassCacheableService implements CacheableService {
public Long throwUnchecked(Object arg1) {
throw new UnsupportedOperationException();
}
}
// multi annotations
@CacheDefinitions(cacheable = { @Cacheable("primary"), @Cacheable("secondary") })
public Object multiCache(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") })
public Object multiEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(cacheable = { @Cacheable(value = "primary", key = "#root.methodName") }, evict = { @CacheEvict("secondary") })
public Object multiCacheAndEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(cacheable = { @Cacheable(value = "primary", condition = "#p0 == 3") }, evict = { @CacheEvict("secondary") })
public Object multiConditionalCacheAndEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(put = { @CachePut("primary"), @CachePut("secondary") })
public Object multiUpdate(Object arg1) {
return arg1;
}
}

View File

@ -24,7 +24,7 @@ import org.springframework.cache.interceptor.CacheInterceptor;
/**
* @author Costin Leau
*/
public abstract class AbstractCacheAdviceNamespaceTests extends AbstractAnnotationTests {
public class CacheAdviceNamespaceTests extends AbstractAnnotationTests {
@Override

View File

@ -50,4 +50,14 @@ public interface CacheableService<T> {
T throwUnchecked(Object arg1);
// multi annotations
T multiCache(Object arg1);
T multiEvict(Object arg1);
T multiCacheAndEvict(Object arg1);
T multiConditionalCacheAndEvict(Object arg1);
T multiUpdate(Object arg1);
}

View File

@ -18,8 +18,9 @@ package org.springframework.cache.config;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.cache.annotation.CacheDefinitions;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CacheUpdate;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
/**
@ -65,12 +66,12 @@ public class DefaultCacheableService implements CacheableService<Long> {
return counter.getAndIncrement();
}
@CacheUpdate("default")
@CachePut("default")
public Long update(Object arg1) {
return counter.getAndIncrement();
}
@CacheUpdate(value = "default", condition = "#arg.equals(3)")
@CachePut(value = "default", condition = "#arg.equals(3)")
public Long conditionalUpdate(Object arg) {
return Long.valueOf(arg.toString());
}
@ -94,4 +95,31 @@ public class DefaultCacheableService implements CacheableService<Long> {
public Long throwUnchecked(Object arg1) {
throw new UnsupportedOperationException(arg1.toString());
}
// multi annotations
@CacheDefinitions(cacheable = { @Cacheable("primary"), @Cacheable("secondary") })
public Long multiCache(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(evict = { @CacheEvict("primary"), @CacheEvict(value = "secondary", key = "#p0") })
public Long multiEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(cacheable = { @Cacheable(value = "primary", key = "#root.methodName") }, evict = { @CacheEvict("secondary") })
public Long multiCacheAndEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(cacheable = { @Cacheable(value = "primary", condition = "#p0 == 3") }, evict = { @CacheEvict("secondary") })
public Long multiConditionalCacheAndEvict(Object arg1) {
return counter.getAndIncrement();
}
@CacheDefinitions(put = { @CachePut("primary"), @CachePut("secondary") })
public Long multiUpdate(Object arg1) {
return Long.valueOf(arg1.toString());
}
}

View File

@ -30,6 +30,8 @@
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="primary"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="secondary"/>
</set>
</property>
</bean>

View File

@ -17,6 +17,8 @@
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="primary"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="secondary"/>
</set>
</property>
</bean>

View File

@ -20,6 +20,26 @@
<cache:cache-evict method="invalidate" cache="default"/>
<cache:cache-evict method="evict" key="#p0" cache="default"/>
</cache:definitions>
<cache:definitions cache="default">
<cache:cache-put method="update"/>
<cache:cache-put method="conditionalUpdate" condition="#arg.equals(3)"/>
</cache:definitions>
<cache:definitions method="mult*Cache">
<cache:cacheable cache="primary"/>
<cache:cacheable cache="secondary"/>
</cache:definitions>
<cache:definitions method="multiEvict">
<cache:cache-evict cache="primary"/>
<cache:cache-evict method="multiEvict" cache="secondary" key="#p0"/>
</cache:definitions>
<cache:definitions>
<cache:cacheable method="multiCacheAndEvict" cache="primary" key="#root.methodName"/>
<cache:cache-evict method="multiCacheAndEvict" cache="secondary"/>
<cache:cacheable method="multiConditionalCacheAndEvict" cache="primary" condition="#p0 == 3"/>
<cache:cache-evict method="multiConditionalCacheAndEvict" cache="secondary"/>
<cache:cache-put method="multiUpdate" cache="primary"/>
<cache:cache-put method="multiUpdate" cache="secondary"/>
</cache:definitions>
</cache:advice>
<cache:advice id="cacheAdviceClass" cache-manager="cacheManager" key-generator="keyGenerator">
@ -35,6 +55,22 @@
<cache:cache-evict method="invalidate" cache="default"/>
<cache:cache-evict method="evict" key="#p0" cache="default"/>
</cache:definitions>
<cache:definitions cache="default">
<cache:cache-put method="update"/>
<cache:cache-put method="conditionalUpdate" condition="#arg.equals(3)"/>
</cache:definitions>
<cache:definitions>
<cache:cacheable method="multiCache" cache="primary"/>
<cache:cacheable method="multiCache" cache="secondary"/>
<cache:cache-evict method="multiEvict" cache="primary"/>
<cache:cache-evict method="multiEvict" cache="secondary" key="#p0"/>
<cache:cacheable method="multiCacheAndEvict" cache="primary" key="#root.methodName"/>
<cache:cache-evict method="multiCacheAndEvict" cache="secondary"/>
<cache:cacheable method="multiConditionalCacheAndEvict" cache="primary" condition="#p0 == 3"/>
<cache:cache-evict method="multiConditionalCacheAndEvict" cache="secondary"/>
<cache:cache-put method="multiUpdate" cache="primary"/>
<cache:cache-put method="multiUpdate" cache="secondary"/>
</cache:definitions>
</cache:advice>
<aop:config>
@ -47,6 +83,8 @@
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="primary"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="secondary"/>
</set>
</property>
</bean>