Skip to content

Commit dc88a7c

Browse files
author
Costin Leau
committed
SPR-8830
SPR-8082 SPR-7833 + add support for CacheDefinitions declarations inside XML + more integration tests
1 parent e4c8855 commit dc88a7c

File tree

20 files changed

+497
-175
lines changed

20 files changed

+497
-175
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
3+
<xsd:schema xmlns="http://www.springframework.org/schema/cache"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
5+
xmlns:beans="http://www.springframework.org/schema/beans"
6+
xmlns:tool="http://www.springframework.org/schema/tool"
7+
targetNamespace="http://www.springframework.org/schema/cache"
8+
elementFormDefault="qualified"
9+
attributeFormDefault="unqualified">
10+
11+
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"/>
12+
<xsd:import namespace="http://www.springframework.org/schema/tool" schemaLocation="http://www.springframework.org/schema/tool/spring-tool-3.0.xsd"/>
13+
14+
<xsd:annotation>
15+
<xsd:documentation><![CDATA[
16+
Defines the elements used in the Spring Framework's declarative
17+
cache management infrastructure.
18+
]]></xsd:documentation>
19+
</xsd:annotation>
20+
21+
<xsd:element name="annotation-driven">
22+
<xsd:complexType>
23+
<xsd:annotation>
24+
<xsd:documentation source="java:org.springframework.cache.annotation.AnnotationCacheOperationDefinitionSource"><![CDATA[
25+
Indicates that cache configuration is defined by Java 5
26+
annotations on bean classes, and that proxies are automatically
27+
to be created for the relevant annotated beans.
28+
29+
The default annotations supported are Spring's @Cacheable and @CacheEvict.
30+
]]></xsd:documentation>
31+
</xsd:annotation>
32+
<xsd:attribute name="cache-manager" type="xsd:string" default="cacheManager">
33+
<xsd:annotation>
34+
<xsd:documentation source="java:org.springframework.cache.CacheManager"><![CDATA[
35+
The bean name of the CacheManager that is to be used to retrieve the backing caches.
36+
37+
This attribute is not required, and only needs to be specified
38+
explicitly if the bean name of the desired CacheManager
39+
is not 'cacheManager'.
40+
]]></xsd:documentation>
41+
<xsd:appinfo>
42+
<tool:annotation kind="ref">
43+
<tool:expected-type type="org.springframework.cache.CacheManager"/>
44+
</tool:annotation>
45+
</xsd:appinfo>
46+
</xsd:annotation>
47+
</xsd:attribute>
48+
<xsd:attribute name="key-generator" type="xsd:string">
49+
<xsd:annotation>
50+
<xsd:documentation source="java:org.springframework.cache.interceptor.KeyGenerator"><![CDATA[
51+
The bean name of the KeyGenerator that is to be used to retrieve the backing caches.
52+
53+
This attribute is not required, and only needs to be specified
54+
explicitly if the default strategy (DefaultKeyGenerator) is not sufficient.
55+
]]></xsd:documentation>
56+
<xsd:appinfo>
57+
<tool:annotation kind="ref">
58+
<tool:expected-type type="org.springframework.cache.interceptor.KeyGenerator"/>
59+
</tool:annotation>
60+
</xsd:appinfo>
61+
</xsd:annotation>
62+
</xsd:attribute>
63+
<xsd:attribute name="mode" default="proxy">
64+
<xsd:annotation>
65+
<xsd:documentation><![CDATA[
66+
Should annotated beans be proxied using Spring's AOP framework,
67+
or should they rather be weaved with an AspectJ transaction aspect?
68+
69+
AspectJ weaving requires spring-aspects.jar on the classpath,
70+
as well as load-time weaving (or compile-time weaving) enabled.
71+
72+
Note: The weaving-based aspect requires the @Cacheable and @CacheInvalidate
73+
annotations to be defined on the concrete class. Annotations in interfaces
74+
will not work in that case (they will rather only work with interface-based proxies)!
75+
]]></xsd:documentation>
76+
</xsd:annotation>
77+
<xsd:simpleType>
78+
<xsd:restriction base="xsd:string">
79+
<xsd:enumeration value="proxy"/>
80+
<xsd:enumeration value="aspectj"/>
81+
</xsd:restriction>
82+
</xsd:simpleType>
83+
</xsd:attribute>
84+
<xsd:attribute name="proxy-target-class" type="xsd:boolean" default="false">
85+
<xsd:annotation>
86+
<xsd:documentation><![CDATA[
87+
Are class-based (CGLIB) proxies to be created? By default, standard
88+
Java interface-based proxies are created.
89+
90+
Note: Class-based proxies require the @Cacheable and @CacheInvalidate annotations
91+
to be defined on the concrete class. Annotations in interfaces will not work
92+
in that case (they will rather only work with interface-based proxies)!
93+
]]></xsd:documentation>
94+
</xsd:annotation>
95+
</xsd:attribute>
96+
<xsd:attribute name="order" type="xsd:int">
97+
<xsd:annotation>
98+
<xsd:documentation source="java:org.springframework.core.Ordered"><![CDATA[
99+
Controls the ordering of the execution of the cache advisor
100+
when multiple advice executes at a specific joinpoint.
101+
]]></xsd:documentation>
102+
</xsd:annotation>
103+
</xsd:attribute>
104+
</xsd:complexType>
105+
</xsd:element>
106+
107+
</xsd:schema>

org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
* Implementation of the {@link org.springframework.cache.interceptor.CacheOperationSource}
3535
* interface for working with caching metadata in JDK 1.5+ annotation format.
3636
*
37-
* <p>This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CacheUpdate} and {@link CacheEvict}
37+
* <p>This class reads Spring's JDK 1.5+ {@link Cacheable}, {@link CachePut} and {@link CacheEvict}
3838
* annotations and exposes corresponding caching operation definition to Spring's cache infrastructure.
3939
* This class may also serve as base class for a custom CacheOperationSource.
4040
*

org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* Strategy interface for parsing known caching annotation types.
2727
* {@link AnnotationCacheDefinitionSource} delegates to such
2828
* parsers for supporting specific annotation types such as Spring's own
29-
* {@link Cacheable}, {@link CacheUpdate} or {@link CacheEvict}.
29+
* {@link Cacheable}, {@link CachePut} or {@link CacheEvict}.
3030
*
3131
* @author Costin Leau
3232
* @since 3.1
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import java.lang.annotation.Target;
2525

2626
/**
27-
* Group annotation for multiple cacheable annotations (of different or the same type).
27+
* Group annotation for multiple cache annotations (of different or the same type).
2828
*
2929
* @author Costin Leau
3030
* @since 3.1
@@ -33,11 +33,11 @@
3333
@Retention(RetentionPolicy.RUNTIME)
3434
@Inherited
3535
@Documented
36-
public @interface CacheDefinition {
36+
public @interface CacheDefinitions {
3737

38-
Cacheable[] cacheables();
38+
Cacheable[] cacheable() default {};
3939

40-
CacheUpdate[] updates();
40+
CachePut[] put() default {};
4141

42-
CacheEvict[] evicts();
42+
CacheEvict[] evict() default {};
4343
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.springframework.cache.Cache;
27+
2628
/**
2729
*
2830
* Annotation indicating that a method (or all methods on a class) trigger(s)
29-
* a cache update operation. As opposed to {@link Cacheable} annotation, this annotation
30-
* does not cause the target method to be skipped in case of a cache hit - rather it
31+
* a {@link Cache#put(Object, Object)} operation. As opposed to {@link Cacheable} annotation,
32+
* this annotation does not cause the target method to be skipped - rather it
3133
* always causes the method to be invoked and its result to be placed into the cache.
3234
*
3335
* @author Costin Leau
@@ -37,7 +39,7 @@
3739
@Retention(RetentionPolicy.RUNTIME)
3840
@Inherited
3941
@Documented
40-
public @interface CacheUpdate {
42+
public @interface CachePut {
4143

4244
/**
4345
* Name of the caches in which the update takes place.

org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323

2424
import org.springframework.cache.interceptor.CacheEvictOperation;
2525
import org.springframework.cache.interceptor.CacheOperation;
26-
import org.springframework.cache.interceptor.CacheUpdateOperation;
26+
import org.springframework.cache.interceptor.CachePutOperation;
2727
import org.springframework.cache.interceptor.CacheableOperation;
2828
import org.springframework.core.annotation.AnnotationUtils;
2929
import org.springframework.util.ObjectUtils;
3030

3131
/**
32-
* Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CacheUpdate} annotations.
32+
* Strategy implementation for parsing Spring's {@link Cacheable}, {@link CacheEvict} and {@link CachePut} annotations.
3333
*
3434
* @author Costin Leau
3535
* @author Juergen Hoeller
@@ -51,12 +51,12 @@ public Collection<CacheOperation> parseCacheAnnotations(AnnotatedElement ae) {
5151
ops = lazyInit(ops);
5252
ops.add(parseEvictAnnotation(ae, evict));
5353
}
54-
CacheUpdate update = AnnotationUtils.getAnnotation(ae, CacheUpdate.class);
54+
CachePut update = AnnotationUtils.getAnnotation(ae, CachePut.class);
5555
if (update != null) {
5656
ops = lazyInit(ops);
5757
ops.add(parseUpdateAnnotation(ae, update));
5858
}
59-
CacheDefinition definition = AnnotationUtils.getAnnotation(ae, CacheDefinition.class);
59+
CacheDefinitions definition = AnnotationUtils.getAnnotation(ae, CacheDefinitions.class);
6060
if (definition != null) {
6161
ops = lazyInit(ops);
6262
ops.addAll(parseDefinitionAnnotation(ae, definition));
@@ -87,36 +87,36 @@ CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, CacheEvict ann) {
8787
return ceo;
8888
}
8989

90-
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CacheUpdate ann) {
91-
CacheUpdateOperation cuo = new CacheUpdateOperation();
90+
CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut ann) {
91+
CachePutOperation cuo = new CachePutOperation();
9292
cuo.setCacheNames(ann.value());
9393
cuo.setCondition(ann.condition());
9494
cuo.setKey(ann.key());
9595
cuo.setName(ae.toString());
9696
return cuo;
9797
}
9898

99-
Collection<CacheOperation> parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinition ann) {
99+
Collection<CacheOperation> parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinitions ann) {
100100
Collection<CacheOperation> ops = null;
101101

102-
Cacheable[] cacheables = ann.cacheables();
102+
Cacheable[] cacheables = ann.cacheable();
103103
if (!ObjectUtils.isEmpty(cacheables)) {
104104
ops = lazyInit(ops);
105105
for (Cacheable cacheable : cacheables) {
106106
ops.add(parseCacheableAnnotation(ae, cacheable));
107107
}
108108
}
109-
CacheEvict[] evicts = ann.evicts();
109+
CacheEvict[] evicts = ann.evict();
110110
if (!ObjectUtils.isEmpty(evicts)) {
111111
ops = lazyInit(ops);
112112
for (CacheEvict evict : evicts) {
113113
ops.add(parseEvictAnnotation(ae, evict));
114114
}
115115
}
116-
CacheUpdate[] updates = ann.updates();
116+
CachePut[] updates = ann.put();
117117
if (!ObjectUtils.isEmpty(updates)) {
118118
ops = lazyInit(ops);
119-
for (CacheUpdate update : updates) {
119+
for (CachePut update : updates) {
120120
ops.add(parseUpdateAnnotation(ae, update));
121121
}
122122
}

org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.cache.config;
1818

19+
import java.util.ArrayList;
20+
import java.util.Collection;
1921
import java.util.List;
2022

2123
import org.springframework.beans.factory.config.TypedStringValue;
@@ -30,6 +32,7 @@
3032
import org.springframework.cache.interceptor.CacheEvictOperation;
3133
import org.springframework.cache.interceptor.CacheInterceptor;
3234
import org.springframework.cache.interceptor.CacheOperation;
35+
import org.springframework.cache.interceptor.CachePutOperation;
3336
import org.springframework.cache.interceptor.CacheableOperation;
3437
import org.springframework.cache.interceptor.NameMatchCacheOperationSource;
3538
import org.springframework.util.StringUtils;
@@ -52,13 +55,14 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
5255
*/
5356
private static class Props {
5457

55-
private String key, condition;
58+
private String key, condition, method;
5659
private String[] caches = null;
5760

5861
Props(Element root) {
5962
String defaultCache = root.getAttribute("cache");
6063
key = root.getAttribute("key");
6164
condition = root.getAttribute("condition");
65+
method = root.getAttribute(METHOD_ATTRIBUTE);
6266

6367
if (StringUtils.hasText(defaultCache)) {
6468
caches = StringUtils.commaDelimitedListToStringArray(defaultCache.trim());
@@ -95,10 +99,24 @@ CacheOperation merge(Element element, ReaderContext readerCtx, CacheOperation op
9599

96100
return op;
97101
}
102+
103+
String merge(Element element, ReaderContext readerCtx) {
104+
String m = element.getAttribute(METHOD_ATTRIBUTE);
105+
106+
if (StringUtils.hasText(m)) {
107+
return m.trim();
108+
}
109+
if (StringUtils.hasText(method)) {
110+
return method;
111+
}
112+
readerCtx.error("No method specified for " + element.getNodeName(), element);
113+
return null;
114+
}
98115
}
99116

100117
private static final String CACHEABLE_ELEMENT = "cacheable";
101118
private static final String CACHE_EVICT_ELEMENT = "cache-evict";
119+
private static final String CACHE_PUT_ELEMENT = "cache-put";
102120
private static final String METHOD_ATTRIBUTE = "method";
103121
private static final String DEFS_ELEMENT = "definitions";
104122

@@ -139,34 +157,60 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte
139157
Props prop = new Props(definition);
140158
// add cacheable first
141159

142-
ManagedMap<TypedStringValue, CacheOperation> cacheOpeMap = new ManagedMap<TypedStringValue, CacheOperation>();
143-
cacheOpeMap.setSource(parserContext.extractSource(definition));
160+
ManagedMap<TypedStringValue, Collection<CacheOperation>> cacheOpMap = new ManagedMap<TypedStringValue, Collection<CacheOperation>>();
161+
cacheOpMap.setSource(parserContext.extractSource(definition));
144162

145-
List<Element> updateCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
163+
List<Element> cacheableCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHEABLE_ELEMENT);
146164

147-
for (Element opElement : updateCacheMethods) {
148-
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
165+
for (Element opElement : cacheableCacheMethods) {
166+
String name = prop.merge(opElement, parserContext.getReaderContext());
149167
TypedStringValue nameHolder = new TypedStringValue(name);
150168
nameHolder.setSource(parserContext.extractSource(opElement));
151169
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
152170

153-
cacheOpeMap.put(nameHolder, op);
171+
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
172+
if (col == null) {
173+
col = new ArrayList<CacheOperation>(2);
174+
cacheOpMap.put(nameHolder, col);
175+
}
176+
col.add(op);
154177
}
155178

156179
List<Element> evictCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_EVICT_ELEMENT);
157180

158181
for (Element opElement : evictCacheMethods) {
159-
String name = opElement.getAttribute(METHOD_ATTRIBUTE);
182+
String name = prop.merge(opElement, parserContext.getReaderContext());
160183
TypedStringValue nameHolder = new TypedStringValue(name);
161184
nameHolder.setSource(parserContext.extractSource(opElement));
162185
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheEvictOperation());
163186

164-
cacheOpeMap.put(nameHolder, op);
187+
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
188+
if (col == null) {
189+
col = new ArrayList<CacheOperation>(2);
190+
cacheOpMap.put(nameHolder, col);
191+
}
192+
col.add(op);
193+
}
194+
195+
List<Element> putCacheMethods = DomUtils.getChildElementsByTagName(definition, CACHE_PUT_ELEMENT);
196+
197+
for (Element opElement : putCacheMethods) {
198+
String name = prop.merge(opElement, parserContext.getReaderContext());
199+
TypedStringValue nameHolder = new TypedStringValue(name);
200+
nameHolder.setSource(parserContext.extractSource(opElement));
201+
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
202+
203+
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
204+
if (col == null) {
205+
col = new ArrayList<CacheOperation>(2);
206+
cacheOpMap.put(nameHolder, col);
207+
}
208+
col.add(op);
165209
}
166210

167211
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchCacheOperationSource.class);
168212
attributeSourceDefinition.setSource(parserContext.extractSource(definition));
169-
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpeMap);
213+
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
170214
return attributeSourceDefinition;
171215
}
172216
}

org.springframework.context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ private Map<String, Collection<CacheOperationContext>> createOperationContext(Co
403403
evicts.add(opContext);
404404
}
405405

406-
if (cacheOperation instanceof CacheUpdateOperation) {
406+
if (cacheOperation instanceof CachePutOperation) {
407407
updates.add(opContext);
408408
}
409409
}

0 commit comments

Comments
 (0)