diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java
index 05f9d8a3620..0851d702656 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/AnnotationCacheOperationSource.java
@@ -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.
*
- *
This class reads Spring's JDK 1.5+ {@link Cacheable} and {@link CacheEvict}
+ *
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 findCacheOperations(Class> clazz) {
+ return determineCacheOperations(clazz);
}
@Override
- protected CacheOperation findCacheOperation(Method method) {
- return determineCacheOperation(method);
+ protected Collection findCacheOperations(Method method) {
+ return determineCacheOperations(method);
}
/**
@@ -103,14 +105,19 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
* @return CacheOperation the configured caching operation,
* or null if none was found
*/
- protected CacheOperation determineCacheOperation(AnnotatedElement ae) {
+ protected Collection determineCacheOperations(AnnotatedElement ae) {
+ Collection ops = null;
+
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
- CacheOperation attr = annotationParser.parseCacheAnnotation(ae);
- if (attr != null) {
- return attr;
+ Collection annOps = annotationParser.parseCacheAnnotations(ae);
+ if (annOps != null) {
+ if (ops == null) {
+ ops = new ArrayList();
+ }
+ ops.addAll(annOps);
}
}
- return null;
+ return ops;
}
/**
@@ -120,5 +127,4 @@ public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperati
protected boolean allowPublicMethodsOnly() {
return this.publicMethodsOnly;
}
-
-}
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
index b016f3009cd..44117112c71 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheAnnotationParser.java
@@ -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 null if none was found
* @see AnnotationCacheOperationSource#determineCacheOperation
*/
- CacheOperation parseCacheAnnotation(AnnotatedElement ae);
-
+ Collection parseCacheAnnotations(AnnotatedElement ae);
}
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java
new file mode 100644
index 00000000000..7151a3470d1
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheDefinition.java
@@ -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();
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java
new file mode 100644
index 00000000000..fee6fb19c7f
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/CacheUpdate.java
@@ -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.
+ *
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.
+ *
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.
+ *
Default is "", meaning the method result is always cached.
+ */
+ String condition() default "";
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/Cacheable.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/Cacheable.java
index 132352dbaee..c6a33e9a5d7 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/Cacheable.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/Cacheable.java
@@ -56,5 +56,4 @@ public @interface Cacheable {
*
Default is "", meaning the method is always cached.
*/
String condition() default "";
-
}
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
index df99b7f73ec..0ec6c4b5c49 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java
@@ -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 parseCacheAnnotations(AnnotatedElement ae) {
+ Collection 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 lazyInit(Collection ops) {
+ return (ops != null ? ops : new ArrayList(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 parseDefinitionAnnotation(AnnotatedElement ae, CacheDefinition ann) {
+ Collection 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;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/annotation/package-info.java b/org.springframework.context/src/main/java/org/springframework/cache/annotation/package-info.java
index f61fb1e579e..e9d9a11f3bd 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/annotation/package-info.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/annotation/package-info.java
@@ -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;
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java b/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
index ae8de0310f7..473442ceb67 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java
@@ -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);
}
diff --git a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
index 5387f8ba832..3d8568c9303 100644
--- a/org.springframework.context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
+++ b/org.springframework.context/src/main/java/org/springframework/cache/interceptor/AbstractFallbackCacheOperationSource.java
@@ -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 NULL_CACHING_ATTRIBUTE = Collections.emptyList();
/**
* Logger available to subclasses.
@@ -65,7 +67,7 @@ public abstract class AbstractFallbackCacheOperationSource implements CacheOpera
*
As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
- final Map