Skip to content

Commit

Permalink
Custom KeyGenerator
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed Mar 31, 2014
1 parent 906321d commit 81c2080
Show file tree
Hide file tree
Showing 25 changed files with 407 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -105,6 +105,18 @@ public Object rootVars(Object arg1) {
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -109,6 +109,18 @@ public Long rootVars(Object arg1) {
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
</property>
</bean>

<bean id="customKeyGenerator" class="org.springframework.cache.config.SomeCustomKeyGenerator" />

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="service" class="org.springframework.cache.config.DefaultCacheableService"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -45,10 +45,17 @@

/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p>Default is "", meaning all method parameters are considered as a key.
* <p>Default is "", meaning all method parameters are considered as a key, unless
* a custom {@link #keyGenerator()} has been set.
*/
String key() default "";

/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} to use.
* <p>Mutually exclusive with the {@link #key()} attribute.
*/
String keyGenerator() default "";

/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
* <p>Default is "", meaning the method is always cached.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -50,10 +50,17 @@

/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p>Default is "", meaning all method parameters are considered as a key.
* <p>Default is "", meaning all method parameters are considered as a key, unless
* a custom {@link #keyGenerator()} has been set.
*/
String key() default "";

/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} to use.
* <p>Mutually exclusive with the {@link #key()} attribute.
*/
String keyGenerator() default "";

/**
* Spring Expression Language (SpEL) attribute used for conditioning the cache update.
* <p>Default is "", meaning the method result is always cached.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -48,10 +48,17 @@

/**
* Spring Expression Language (SpEL) attribute for computing the key dynamically.
* <p>Default is "", meaning all method parameters are considered as a key.
* <p>Default is "", meaning all method parameters are considered as a key, unless
* a custom {@link #keyGenerator()} has been set.
*/
String key() default "";

/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} to use.
* <p>Mutually exclusive with the {@link #key()} attribute.
*/
String keyGenerator() default "";

/**
* Spring Expression Language (SpEL) attribute used for conditioning the method caching.
* <p>Default is "", meaning the method is always cached.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,6 +27,7 @@
import org.springframework.cache.interceptor.CachePutOperation;
import org.springframework.cache.interceptor.CacheableOperation;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
* Strategy implementation for parsing Spring's {@link Caching}, {@link Cacheable},
Expand Down Expand Up @@ -86,7 +87,10 @@ CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable cachi
cuo.setCondition(caching.condition());
cuo.setUnless(caching.unless());
cuo.setKey(caching.key());
cuo.setKeyGenerator(caching.keyGenerator());
cuo.setName(ae.toString());

checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
return cuo;
}

Expand All @@ -95,9 +99,12 @@ CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, CacheEvict caching
ceo.setCacheNames(caching.value());
ceo.setCondition(caching.condition());
ceo.setKey(caching.key());
ceo.setKeyGenerator(caching.keyGenerator());
ceo.setCacheWide(caching.allEntries());
ceo.setBeforeInvocation(caching.beforeInvocation());
ceo.setName(ae.toString());

checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
return ceo;
}

Expand All @@ -107,7 +114,10 @@ CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut caching) {
cuo.setCondition(caching.condition());
cuo.setUnless(caching.unless());
cuo.setKey(caching.key());
cuo.setKeyGenerator(caching.keyGenerator());
cuo.setName(ae.toString());

checkKeySourceConsistency(ae, caching.key(), caching.keyGenerator());
return cuo;
}

Expand Down Expand Up @@ -159,6 +169,15 @@ private <T extends Annotation> Collection<T> getAnnotations(AnnotatedElement ae,
return (anns.isEmpty() ? null : anns);
}

private void checkKeySourceConsistency(AnnotatedElement ae, String key, String keyGenerator) {
if (StringUtils.hasText(key) && StringUtils.hasText(keyGenerator)) {
throw new IllegalStateException("Invalid cache annotation configuration on '"
+ ae.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}
}

@Override
public boolean equals(Object other) {
return (this == other || other instanceof SpringCacheAnnotationParser);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -168,7 +168,7 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte

private static String getAttributeValue(Element element, String attributeName, String defaultValue) {
String attribute = element.getAttribute(attributeName);
if(StringUtils.hasText(attribute)) {
if (StringUtils.hasText(attribute)) {
return attribute.trim();
}
return defaultValue;
Expand All @@ -184,6 +184,8 @@ private static class Props {

private String key;

private String keyGenerator;

private String condition;

private String method;
Expand All @@ -194,6 +196,7 @@ private static class Props {
Props(Element root) {
String defaultCache = root.getAttribute("cache");
key = root.getAttribute("key");
keyGenerator = root.getAttribute("key-generator");
condition = root.getAttribute("condition");
method = root.getAttribute(METHOD_ATTRIBUTE);

Expand All @@ -218,8 +221,16 @@ <T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T o
op.setCacheNames(localCaches);

op.setKey(getAttributeValue(element, "key", this.key));
op.setKeyGenerator(getAttributeValue(element, "key-generator", this.keyGenerator));
op.setCondition(getAttributeValue(element, "condition", this.condition));

if (StringUtils.hasText(op.getKey()) && StringUtils.hasText(op.getKeyGenerator())) {
throw new IllegalStateException("Invalid cache advice configuration on '"
+ element.toString() + "'. Both 'key' and 'keyGenerator' attributes have been set. " +
"These attributes are mutually exclusive: either set the SpEL expression used to" +
"compute the key at runtime or set the name of the KeyGenerator bean to use.");
}

return op;
}

Expand Down
Loading

0 comments on commit 81c2080

Please sign in to comment.