Custom KeyGenerator
This commit adds an extra parameter to the base @Cache method annotations: keyGenerator. This parameter holds the name of the KeyGenerator bean to use to compute the key for that specific caching endpoint. This gives therefore a third way to customize the key. These are: 1. Default KeyGenerator (global for all endpoints) 2. The 'key' attribute of the annotation, giving the SpEL expression to use 3. The 'keyGenerator' attribute of the annotation The annotation attributes are therefore exclusive. Trying to specify them both will result in an IllegalStateException. The KeyGenerator to use for a given operation is cached on startup so that multiple calls to it does not resolve the instance to use over and over again. Issue: SPR-10629
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011 the original author or authors.
|
||||
* Copyright 2011-2014 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.
|
||||
@@ -90,6 +90,32 @@ public class AnnotationCacheOperationSourceTests {
|
||||
assertTrue(next.getCacheNames().contains("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomKeyGenerator() {
|
||||
Collection<CacheOperation> ops = getOps("customKeyGenerator");
|
||||
assertEquals(1, ops.size());
|
||||
CacheOperation cacheOperation = ops.iterator().next();
|
||||
assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomKeyGeneratorInherited() {
|
||||
Collection<CacheOperation> ops = getOps("customKeyGeneratorInherited");
|
||||
assertEquals(1, ops.size());
|
||||
CacheOperation cacheOperation = ops.iterator().next();
|
||||
assertEquals("Custom key generator not set", "custom", cacheOperation.getKeyGenerator());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyAndKeyGeneratorCannotBeSetTogether() {
|
||||
try {
|
||||
getOps("invalidKeyAndKeyGeneratorSet");
|
||||
fail("Should have failed to parse @Cacheable annotation");
|
||||
} catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private static class AnnotatedClass {
|
||||
@Cacheable("test")
|
||||
public void singular() {
|
||||
@@ -100,13 +126,16 @@ public class AnnotationCacheOperationSourceTests {
|
||||
public void multiple() {
|
||||
}
|
||||
|
||||
@Caching(cacheable = { @Cacheable("test") }, evict = { @CacheEvict("test") })
|
||||
@Caching(cacheable = {@Cacheable("test")}, evict = {@CacheEvict("test")})
|
||||
public void caching() {
|
||||
}
|
||||
|
||||
@Cacheable(value = "test", keyGenerator = "custom")
|
||||
public void customKeyGenerator() {
|
||||
}
|
||||
|
||||
@EvictFoo
|
||||
public void singleStereotype() {
|
||||
|
||||
}
|
||||
|
||||
@EvictFoo
|
||||
@@ -115,9 +144,17 @@ public class AnnotationCacheOperationSourceTests {
|
||||
public void multipleStereotype() {
|
||||
}
|
||||
|
||||
@Caching(cacheable = { @Cacheable(value = "test", key = "a"), @Cacheable(value = "test", key = "b") })
|
||||
@Caching(cacheable = {@Cacheable(value = "test", key = "a"), @Cacheable(value = "test", key = "b")})
|
||||
public void multipleCaching() {
|
||||
}
|
||||
|
||||
@CacheableFooCustomKeyGenerator
|
||||
public void customKeyGeneratorInherited() {
|
||||
}
|
||||
|
||||
@Cacheable(value = "test", key = "#root.methodName", keyGenerator = "custom")
|
||||
public void invalidKeyAndKeyGeneratorSet() {
|
||||
}
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -126,6 +163,12 @@ public class AnnotationCacheOperationSourceTests {
|
||||
public @interface CacheableFoo {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Cacheable(value = "foo", keyGenerator = "custom")
|
||||
public @interface CacheableFooCustomKeyGenerator {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@CacheEvict(value = "foo")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
package org.springframework.cache.config;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -26,6 +25,7 @@ import java.util.UUID;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -569,6 +569,28 @@ public abstract class AbstractAnnotationTests {
|
||||
testRootVars(ccs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomKeyGenerator() {
|
||||
Object param = new Object();
|
||||
Object r1 = cs.customKeyGenerator(param);
|
||||
assertSame(r1, cs.customKeyGenerator(param));
|
||||
Cache cache = cm.getCache("default");
|
||||
// Checks that the custom keyGenerator was used
|
||||
Object expectedKey = SomeCustomKeyGenerator.generateKey("customKeyGenerator", param);
|
||||
assertNotNull(cache.get(expectedKey));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownCustomKeyGenerator() {
|
||||
try {
|
||||
Object param = new Object();
|
||||
cs.unknownCustomKeyGenerator(param);
|
||||
fail("should have failed with NoSuchBeanDefinitionException");
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullArg() throws Exception {
|
||||
testNullArg(cs);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -107,6 +107,18 @@ public class AnnotatedClassCacheableService implements CacheableService<Object>
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "default", keyGenerator = "customKyeGenerator")
|
||||
public Object customKeyGenerator(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "default", keyGenerator = "unknownBeanName")
|
||||
public Object unknownCustomKeyGenerator(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CachePut("default")
|
||||
public Object update(Object arg1) {
|
||||
|
||||
40
spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java
vendored
Normal file
40
spring-context/src/test/java/org/springframework/cache/config/CacheAdviceParserTests.java
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.config;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.support.GenericXmlApplicationContext;
|
||||
|
||||
/**
|
||||
* AOP advice specific parsing tests.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class CacheAdviceParserTests {
|
||||
|
||||
@Test
|
||||
public void keyAndKeyGeneratorCannotBeSetTogether() {
|
||||
try {
|
||||
new GenericXmlApplicationContext("/org/springframework/cache/config/cache-advice-invalid.xml");
|
||||
fail("Should have failed to load context, one advise define both a key and a key generator");
|
||||
} catch (BeanDefinitionStoreException e) { // TODO better exception handling
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -58,6 +58,10 @@ public interface CacheableService<T> {
|
||||
|
||||
T rootVars(Object arg1);
|
||||
|
||||
T customKeyGenerator(Object arg1);
|
||||
|
||||
T unknownCustomKeyGenerator(Object arg1);
|
||||
|
||||
T throwChecked(Object arg1) throws Exception;
|
||||
|
||||
T throwUnchecked(Object arg1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -109,6 +109,18 @@ public class DefaultCacheableService implements CacheableService<Long> {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "default", keyGenerator = "customKeyGenerator")
|
||||
public Long customKeyGenerator(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "default", keyGenerator = "unknownBeanName")
|
||||
public Long unknownCustomKeyGenerator(Object arg1) {
|
||||
return counter.getAndIncrement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CachePut("default")
|
||||
public Long update(Object arg1) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -52,7 +52,7 @@ public class EnableCachingTests extends AbstractAnnotationTests {
|
||||
@Test
|
||||
public void testKeyStrategy() throws Exception {
|
||||
CacheInterceptor ci = ctx.getBean(CacheInterceptor.class);
|
||||
assertSame(ctx.getBean(KeyGenerator.class), ci.getKeyGenerator());
|
||||
assertSame(ctx.getBean("keyGenerator", KeyGenerator.class), ci.getKeyGenerator());
|
||||
}
|
||||
|
||||
// --- local tests -------
|
||||
@@ -140,6 +140,11 @@ public class EnableCachingTests extends AbstractAnnotationTests {
|
||||
public KeyGenerator keyGenerator() {
|
||||
return new SomeKeyGenerator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KeyGenerator customKeyGenerator() {
|
||||
return new SomeCustomKeyGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
46
spring-context/src/test/java/org/springframework/cache/config/SomeCustomKeyGenerator.java
vendored
Normal file
46
spring-context/src/test/java/org/springframework/cache/config/SomeCustomKeyGenerator.java
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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.config;
|
||||
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A custom {@link KeyGenerator} that exposes the algorithm used to compute the key
|
||||
* for convenience in tests scenario.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
final class SomeCustomKeyGenerator implements KeyGenerator {
|
||||
|
||||
@Override
|
||||
public Object generate(Object target, Method method, Object... params) {
|
||||
return generateKey(method.getName(), params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #generate(Object, java.lang.reflect.Method, Object...)
|
||||
*/
|
||||
static Object generateKey(String methodName, Object... params) {
|
||||
final StringBuilder sb = new StringBuilder(methodName);
|
||||
for (Object param : params) {
|
||||
sb.append(param);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user