SpringCacheAnnotationParser properly accepts empty @Caching annotation

Issue: SPR-14162
(cherry picked from commit da11261)
This commit is contained in:
Juergen Hoeller 2016-04-12 23:19:51 +02:00
parent 3829a77894
commit 33dcef3583
2 changed files with 50 additions and 52 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -86,7 +86,10 @@ public class SpringCacheAnnotationParser implements CacheAnnotationParser, Seria
if (cachings != null) { if (cachings != null) {
ops = lazyInit(ops); ops = lazyInit(ops);
for (Caching caching : cachings) { for (Caching caching : cachings) {
ops.addAll(parseCachingAnnotation(ae, cachingConfig, caching)); Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,10 +23,8 @@ import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import org.junit.Ignore;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
@ -37,7 +35,6 @@ import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AliasFor;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
@ -50,7 +47,7 @@ public class AnnotationCacheOperationSourceTests {
@Rule @Rule
public final ExpectedException exception = ExpectedException.none(); public final ExpectedException exception = ExpectedException.none();
private AnnotationCacheOperationSource source = new AnnotationCacheOperationSource(); private final AnnotationCacheOperationSource source = new AnnotationCacheOperationSource();
private Collection<CacheOperation> getOps(Class<?> target, String name, int expectedNumberOfOperations) { private Collection<CacheOperation> getOps(Class<?> target, String name, int expectedNumberOfOperations) {
@ -86,6 +83,11 @@ public class AnnotationCacheOperationSourceTests {
assertTrue(it.next() instanceof CacheEvictOperation); assertTrue(it.next() instanceof CacheEvictOperation);
} }
@Test
public void emptyCaching() throws Exception {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "emptyCaching", 0);
}
@Test @Test
public void singularStereotype() throws Exception { public void singularStereotype() throws Exception {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleStereotype", 1); Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleStereotype", 1);
@ -105,36 +107,6 @@ public class AnnotationCacheOperationSourceTests {
assertTrue(next.getCacheNames().contains("bar")); assertTrue(next.getCacheNames().contains("bar"));
} }
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
@Ignore("Disabled until SPR-13475 is resolved")
@Test
public void singleComposedAnnotation() throws Exception {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "singleComposed", 1);
CacheOperation cacheOperation = ops.iterator().next();
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composed")));
}
// TODO [SPR-13475] Enable test once @Cache* is supported as a composed annotation.
@Ignore("Disabled until SPR-13475 is resolved")
@Test
public void multipleComposedAnnotations() throws Exception {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "multipleComposed", 3);
Iterator<CacheOperation> it = ops.iterator();
CacheOperation cacheOperation = it.next();
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
cacheOperation = it.next();
assertThat(cacheOperation, instanceOf(CacheableOperation.class));
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("foo")));
cacheOperation = it.next();
assertThat(cacheOperation, instanceOf(CacheEvictOperation.class));
assertThat(cacheOperation.getCacheNames(), equalTo(Collections.singleton("composedCache")));
}
@Test @Test
public void customKeyGenerator() { public void customKeyGenerator() {
Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGenerator", 1); Collection<CacheOperation> ops = getOps(AnnotatedClass.class, "customKeyGenerator", 1);
@ -261,17 +233,20 @@ public class AnnotationCacheOperationSourceTests {
assertSharedConfig(cacheOperation, "classKeyGenerator", "classCacheManager", "", "classCacheName"); assertSharedConfig(cacheOperation, "classKeyGenerator", "classCacheManager", "", "classCacheName");
} }
private void assertSharedConfig(CacheOperation actual, String keyGenerator, String cacheManager, private void assertSharedConfig(CacheOperation actual, String keyGenerator, String cacheManager,
String cacheResolver, String... cacheNames) { String cacheResolver, String... cacheNames) {
assertEquals("Wrong key manager", keyGenerator, actual.getKeyGenerator()); assertEquals("Wrong key manager", keyGenerator, actual.getKeyGenerator());
assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager()); assertEquals("Wrong cache manager", cacheManager, actual.getCacheManager());
assertEquals("Wrong cache resolver", cacheResolver, actual.getCacheResolver()); assertEquals("Wrong cache resolver", cacheResolver, actual.getCacheResolver());
assertEquals("Wrong number of cache names", cacheNames.length, actual.getCacheNames().size()); assertEquals("Wrong number of cache names", cacheNames.length, actual.getCacheNames().size());
Arrays.stream(cacheNames).forEach( Arrays.stream(cacheNames).forEach(
cacheName -> assertTrue("Cache '" + cacheName + "' not found in " + actual.getCacheNames(), cacheName -> assertTrue("Cache '" + cacheName + "' not found in " + actual.getCacheNames(),
actual.getCacheNames().contains(cacheName))); actual.getCacheNames().contains(cacheName)));
} }
private static class AnnotatedClass { private static class AnnotatedClass {
@Cacheable("test") @Cacheable("test")
@ -287,6 +262,10 @@ public class AnnotationCacheOperationSourceTests {
public void caching() { public void caching() {
} }
@Caching
public void emptyCaching() {
}
@Cacheable(cacheNames = "test", keyGenerator = "custom") @Cacheable(cacheNames = "test", keyGenerator = "custom")
public void customKeyGenerator() { public void customKeyGenerator() {
} }
@ -309,13 +288,15 @@ public class AnnotationCacheOperationSourceTests {
public void multipleStereotype() { public void multipleStereotype() {
} }
@ComposedCacheable("composed") @Cacheable("directly declared")
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
public void singleComposed() { public void singleComposed() {
} }
@Cacheable("directly declared")
@ComposedCacheable(cacheNames = "composedCache", key = "composedKey") @ComposedCacheable(cacheNames = "composedCache", key = "composedKey")
@CacheableFoo @CacheableFoo
@ComposedCacheEvict(cacheNames = "composedCache", key = "composedKey") @ComposedCacheEvict(cacheNames = "composedCacheEvict", key = "composedEvictionKey")
public void multipleComposed() { public void multipleComposed() {
} }
@ -348,6 +329,7 @@ public class AnnotationCacheOperationSourceTests {
} }
} }
@CacheConfig(cacheNames = "classCacheName", @CacheConfig(cacheNames = "classCacheName",
keyGenerator = "classKeyGenerator", keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver") cacheManager = "classCacheManager", cacheResolver = "classCacheResolver")
@ -370,6 +352,7 @@ public class AnnotationCacheOperationSourceTests {
} }
} }
@CacheConfigFoo @CacheConfigFoo
private static class AnnotatedClassWithCustomDefault { private static class AnnotatedClassWithCustomDefault {
@ -378,6 +361,7 @@ public class AnnotationCacheOperationSourceTests {
} }
} }
@CacheConfig(cacheNames = "classCacheName", @CacheConfig(cacheNames = "classCacheName",
keyGenerator = "classKeyGenerator", keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager") cacheManager = "classCacheManager")
@ -396,6 +380,7 @@ public class AnnotationCacheOperationSourceTests {
} }
} }
@CacheConfigFoo @CacheConfigFoo
@CacheConfig(cacheNames = "myCache") // multiple sources @CacheConfig(cacheNames = "myCache") // multiple sources
private static class MultipleCacheConfig { private static class MultipleCacheConfig {
@ -405,77 +390,87 @@ public class AnnotationCacheOperationSourceTests {
} }
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Cacheable("foo") @Cacheable("foo")
public @interface CacheableFoo { public @interface CacheableFoo {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Cacheable(cacheNames = "foo", keyGenerator = "custom") @Cacheable(cacheNames = "foo", keyGenerator = "custom")
public @interface CacheableFooCustomKeyGenerator { public @interface CacheableFooCustomKeyGenerator {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Cacheable(cacheNames = "foo", cacheManager = "custom") @Cacheable(cacheNames = "foo", cacheManager = "custom")
public @interface CacheableFooCustomCacheManager { public @interface CacheableFooCustomCacheManager {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Cacheable(cacheNames = "foo", cacheResolver = "custom") @Cacheable(cacheNames = "foo", cacheResolver = "custom")
public @interface CacheableFooCustomCacheResolver { public @interface CacheableFooCustomCacheResolver {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@CacheEvict("foo") @CacheEvict("foo")
public @interface EvictFoo { public @interface EvictFoo {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@CacheEvict("bar") @CacheEvict("bar")
public @interface EvictBar { public @interface EvictBar {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@CacheConfig(keyGenerator = "classKeyGenerator", @CacheConfig(keyGenerator = "classKeyGenerator",
cacheManager = "classCacheManager", cacheResolver = "classCacheResolver") cacheManager = "classCacheManager",
cacheResolver = "classCacheResolver")
public @interface CacheConfigFoo { public @interface CacheConfigFoo {
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.TYPE })
@Cacheable(cacheNames = "shadowed cache name", key = "shadowed key") @Cacheable(cacheNames = "shadowed cache name", key = "shadowed key")
public @interface ComposedCacheable { @interface ComposedCacheable {
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames") @AliasFor(annotation = Cacheable.class)
String[] value() default {}; String[] value() default {};
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames") @AliasFor(annotation = Cacheable.class)
String[] cacheNames() default {}; String[] cacheNames() default {};
@AliasFor(annotation = Cacheable.class, attribute = "key") @AliasFor(annotation = Cacheable.class)
String key() default ""; String key() default "";
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.TYPE })
@CacheEvict(cacheNames = "shadowed cache name", key = "shadowed key") @CacheEvict(cacheNames = "shadowed cache name", key = "shadowed key")
public @interface ComposedCacheEvict { @interface ComposedCacheEvict {
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames") @AliasFor(annotation = CacheEvict.class)
String[] value() default {}; String[] value() default {};
@AliasFor(annotation = Cacheable.class, attribute = "cacheNames") @AliasFor(annotation = CacheEvict.class)
String[] cacheNames() default {}; String[] cacheNames() default {};
@AliasFor(annotation = Cacheable.class, attribute = "key") @AliasFor(annotation = CacheEvict.class)
String key() default ""; String key() default "";
} }
} }