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 fd078c1d2bf..ab26c400920 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 @@ -17,6 +17,7 @@ package org.springframework.cache.annotation; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import java.util.Collection; @@ -25,7 +26,6 @@ import org.springframework.cache.interceptor.CacheEvictOperation; import org.springframework.cache.interceptor.CacheOperation; import org.springframework.cache.interceptor.CachePutOperation; import org.springframework.cache.interceptor.CacheableOperation; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ObjectUtils; /** @@ -43,31 +43,39 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria public Collection parseCacheAnnotations(AnnotatedElement ae) { Collection ops = null; - Cacheable cache = AnnotationUtils.getAnnotation(ae, Cacheable.class); - if (cache != null) { + Collection cacheables = getAnnotations(ae, Cacheable.class); + if (cacheables != null) { ops = lazyInit(ops); - ops.add(parseCacheableAnnotation(ae, cache)); + for (Cacheable cacheable : cacheables) { + ops.add(parseCacheableAnnotation(ae, cacheable)); + } } - CacheEvict evict = AnnotationUtils.getAnnotation(ae, CacheEvict.class); - if (evict != null) { + Collection evicts = getAnnotations(ae, CacheEvict.class); + if (evicts != null) { ops = lazyInit(ops); - ops.add(parseEvictAnnotation(ae, evict)); + for (CacheEvict e : evicts) { + ops.add(parseEvictAnnotation(ae, e)); + } } - CachePut update = AnnotationUtils.getAnnotation(ae, CachePut.class); - if (update != null) { + Collection updates = getAnnotations(ae, CachePut.class); + if (updates != null) { ops = lazyInit(ops); - ops.add(parseUpdateAnnotation(ae, update)); + for (CachePut p : updates) { + ops.add(parseUpdateAnnotation(ae, p)); + } } - Caching caching = AnnotationUtils.getAnnotation(ae, Caching.class); + Collection caching = getAnnotations(ae, Caching.class); if (caching != null) { ops = lazyInit(ops); - ops.addAll(parseCachingAnnotation(ae, caching)); + for (Caching c : caching) { + ops.addAll(parseCachingAnnotation(ae, c)); + } } return ops; } - private Collection lazyInit(Collection ops) { - return (ops != null ? ops : new ArrayList(2)); + private Collection lazyInit(Collection ops) { + return (ops != null ? ops : new ArrayList(1)); } CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable caching) { @@ -126,4 +134,24 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria return ops; } -} + + private static Collection getAnnotations(AnnotatedElement ae, Class annotationType) { + Collection anns = new ArrayList(2); + + // look at raw annotation + T ann = ae.getAnnotation(annotationType); + if (ann != null) { + anns.add(ann); + } + + // scan meta-annotations + for (Annotation metaAnn : ae.getAnnotations()) { + ann = metaAnn.annotationType().getAnnotation(annotationType); + if (ann != null) { + anns.add(ann); + } + } + + return anns; + } +} \ No newline at end of file diff --git a/org.springframework.context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTest.java b/org.springframework.context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTest.java new file mode 100644 index 00000000000..6f9cac9cf72 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/cache/annotation/AnnotationCacheOperationSourceTest.java @@ -0,0 +1,136 @@ +/* + * 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 static org.junit.Assert.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Iterator; + +import org.junit.Test; +import org.springframework.cache.interceptor.CacheEvictOperation; +import org.springframework.cache.interceptor.CacheOperation; +import org.springframework.cache.interceptor.CacheableOperation; +import org.springframework.util.ReflectionUtils; + +/** + * @author Costin Leau + */ +public class AnnotationCacheOperationSourceTest { + + private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); + + private Collection getOps(String name) { + Method method = ReflectionUtils.findMethod(AnnotatedClass.class, name); + return source.getCacheOperations(method, AnnotatedClass.class); + } + + @Test + public void testSingularAnnotation() throws Exception { + Collection ops = getOps("singular"); + assertEquals(1, ops.size()); + assertTrue(ops.iterator().next() instanceof CacheableOperation); + } + + @Test + public void testMultipleAnnotation() throws Exception { + Collection ops = getOps("multiple"); + assertEquals(2, ops.size()); + Iterator it = ops.iterator(); + assertTrue(it.next() instanceof CacheableOperation); + assertTrue(it.next() instanceof CacheEvictOperation); + } + + @Test + public void testCaching() throws Exception { + Collection ops = getOps("caching"); + assertEquals(2, ops.size()); + Iterator it = ops.iterator(); + assertTrue(it.next() instanceof CacheableOperation); + assertTrue(it.next() instanceof CacheEvictOperation); + } + + @Test + public void testSingularStereotype() throws Exception { + Collection ops = getOps("singleStereotype"); + assertEquals(1, ops.size()); + assertTrue(ops.iterator().next() instanceof CacheEvictOperation); + } + + @Test + public void testMultipleStereotypes() throws Exception { + Collection ops = getOps("multipleStereotype"); + assertEquals(3, ops.size()); + Iterator it = ops.iterator(); + assertTrue(it.next() instanceof CacheableOperation); + CacheOperation next = it.next(); + assertTrue(next instanceof CacheEvictOperation); + assertTrue(next.getCacheNames().contains("foo")); + next = it.next(); + assertTrue(next instanceof CacheEvictOperation); + assertTrue(next.getCacheNames().contains("bar")); + } + + private static class AnnotatedClass { + @Cacheable("test") + public void singular() { + } + + @CacheEvict("test") + @Cacheable("test") + public void multiple() { + } + + @Caching(cacheable = { @Cacheable("test") }, evict = { @CacheEvict("test") }) + public void caching() { + } + + @EvictFoo + public void singleStereotype() { + + } + + @EvictFoo + @CacheableFoo + @EvictBar + public void multipleStereotype() { + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @Cacheable("foo") + public @interface CacheableFoo { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @CacheEvict(value = "foo") + public @interface EvictFoo { + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @CacheEvict(value = "bar") + public @interface EvictBar { + } +} \ No newline at end of file