Skip to content

Commit 8c2ace3

Browse files
committed
Support 'unless' expression for cache veto
Allow @cachable, @cACHEpUT and equivalent XML configuration to provide a SpEL expression that can be used to veto putting an item into the cache. Unlike 'condition' the 'unless' parameter is evaluated after the method has been called and can therefore reference the #result. For example: @Cacheable(value="book", condition="#name.length < 32", unless="#result.hardback") This commit also allows #result to be referenced from @CacheEvict expressions as long as 'beforeInvocation' is false. Issue: SPR-8871
1 parent 3252cb5 commit 8c2ace3

File tree

22 files changed

+384
-105
lines changed

22 files changed

+384
-105
lines changed

spring-aspects/src/test/java/org/springframework/cache/config/AnnotatedClassCacheableService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525

2626
/**
2727
* @author Costin Leau
28+
* @author Phillip Webb
2829
*/
2930
@Cacheable("default")
3031
public class AnnotatedClassCacheableService implements CacheableService<Object> {
@@ -40,6 +41,10 @@ public Object conditional(int field) {
4041
return null;
4142
}
4243

44+
public Object unless(int arg) {
45+
return arg;
46+
}
47+
4348
@CacheEvict("default")
4449
public void invalidate(Object arg1) {
4550
}

spring-aspects/src/test/java/org/springframework/cache/config/CacheableService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
* Basic service interface.
2121
*
2222
* @author Costin Leau
23+
* @author Phillip Webb
2324
*/
2425
public interface CacheableService<T> {
2526

@@ -39,6 +40,8 @@ public interface CacheableService<T> {
3940

4041
T conditional(int field);
4142

43+
T unless(int arg);
44+
4245
T key(Object arg1, Object arg2);
4346

4447
T name(Object arg1);

spring-aspects/src/test/java/org/springframework/cache/config/DefaultCacheableService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* Simple cacheable service
2828
*
2929
* @author Costin Leau
30+
* @author Phillip Webb
3031
*/
3132
public class DefaultCacheableService implements CacheableService<Long> {
3233

@@ -78,6 +79,12 @@ public Long conditional(int classField) {
7879
return counter.getAndIncrement();
7980
}
8081

82+
@Override
83+
@Cacheable(value = "default", unless = "#result > 10")
84+
public Long unless(int arg) {
85+
return (long) arg;
86+
}
87+
8188
@Override
8289
@Cacheable(value = "default", key = "#p0")
8390
public Long key(Object arg1, Object arg2) {

spring-context/src/main/java/org/springframework/cache/annotation/CachePut.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
* always causes the method to be invoked and its result to be placed into the cache.
3333
*
3434
* @author Costin Leau
35+
* @author Phillip Webb
3536
* @since 3.1
3637
*/
3738
@Target({ ElementType.METHOD, ElementType.TYPE })
@@ -58,4 +59,13 @@
5859
* <p>Default is "", meaning the method result is always cached.
5960
*/
6061
String condition() default "";
62+
63+
/**
64+
* Spring Expression Language (SpEL) attribute used to veto the cache update.
65+
* <p>Unlike {@link #condition()}, this expression is evaluated after the method
66+
* has been called and can therefore refer to the {@code result}. Default is "",
67+
* meaning that caching is never vetoed.
68+
* @since 3.2
69+
*/
70+
String unless() default "";
6171
}

spring-context/src/main/java/org/springframework/cache/annotation/Cacheable.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
* returned instance is used as the cache value.
3131
*
3232
* @author Costin Leau
33+
* @author Phillip Webb
3334
* @since 3.1
3435
*/
3536
@Target({ElementType.METHOD, ElementType.TYPE})
@@ -56,4 +57,13 @@
5657
* <p>Default is "", meaning the method is always cached.
5758
*/
5859
String condition() default "";
60+
61+
/**
62+
* Spring Expression Language (SpEL) attribute used to veto method caching.
63+
* <p>Unlike {@link #condition()}, this expression is evaluated after the method
64+
* has been called and can therefore refer to the {@code result}. Default is "",
65+
* meaning that caching is never vetoed.
66+
* @since 3.2
67+
*/
68+
String unless() default "";
5969
}

spring-context/src/main/java/org/springframework/cache/annotation/SpringCacheAnnotationParser.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
* @author Costin Leau
3636
* @author Juergen Hoeller
3737
* @author Chris Beams
38+
* @author Phillip Webb
3839
* @since 3.1
3940
*/
4041
@SuppressWarnings("serial")
@@ -82,6 +83,7 @@ CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, Cacheable cachi
8283
CacheableOperation cuo = new CacheableOperation();
8384
cuo.setCacheNames(caching.value());
8485
cuo.setCondition(caching.condition());
86+
cuo.setUnless(caching.unless());
8587
cuo.setKey(caching.key());
8688
cuo.setName(ae.toString());
8789
return cuo;
@@ -102,6 +104,7 @@ CacheOperation parseUpdateAnnotation(AnnotatedElement ae, CachePut caching) {
102104
CachePutOperation cuo = new CachePutOperation();
103105
cuo.setCacheNames(caching.value());
104106
cuo.setCondition(caching.condition());
107+
cuo.setUnless(caching.unless());
105108
cuo.setKey(caching.key());
106109
cuo.setName(ae.toString());
107110
return cuo;

spring-context/src/main/java/org/springframework/cache/config/CacheAdviceParser.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@
4444
* BeanDefinitionParser} for the {@code <tx:advice/>} tag.
4545
*
4646
* @author Costin Leau
47+
* @author Phillip Webb
4748
*/
4849
class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
4950

@@ -54,7 +55,9 @@ class CacheAdviceParser extends AbstractSingleBeanDefinitionParser {
5455
*/
5556
private static class Props {
5657

57-
private String key, condition, method;
58+
private String key;
59+
private String condition;
60+
private String method;
5861
private String[] caches = null;
5962

6063
Props(Element root) {
@@ -70,31 +73,20 @@ private static class Props {
7073

7174
<T extends CacheOperation> T merge(Element element, ReaderContext readerCtx, T op) {
7275
String cache = element.getAttribute("cache");
73-
String k = element.getAttribute("key");
74-
String c = element.getAttribute("condition");
75-
76-
String[] localCaches = caches;
77-
String localKey = key, localCondition = condition;
7876

7977
// sanity check
78+
String[] localCaches = caches;
8079
if (StringUtils.hasText(cache)) {
8180
localCaches = StringUtils.commaDelimitedListToStringArray(cache.trim());
8281
} else {
8382
if (caches == null) {
8483
readerCtx.error("No cache specified specified for " + element.getNodeName(), element);
8584
}
8685
}
87-
88-
if (StringUtils.hasText(k)) {
89-
localKey = k.trim();
90-
}
91-
92-
if (StringUtils.hasText(c)) {
93-
localCondition = c.trim();
94-
}
9586
op.setCacheNames(localCaches);
96-
op.setKey(localKey);
97-
op.setCondition(localCondition);
87+
88+
op.setKey(getAttributeValue(element, "key", this.key));
89+
op.setCondition(getAttributeValue(element, "condition", this.condition));
9890

9991
return op;
10092
}
@@ -165,7 +157,8 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte
165157
String name = prop.merge(opElement, parserContext.getReaderContext());
166158
TypedStringValue nameHolder = new TypedStringValue(name);
167159
nameHolder.setSource(parserContext.extractSource(opElement));
168-
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
160+
CacheableOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CacheableOperation());
161+
op.setUnless(getAttributeValue(opElement, "unless", ""));
169162

170163
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
171164
if (col == null) {
@@ -207,7 +200,8 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte
207200
String name = prop.merge(opElement, parserContext.getReaderContext());
208201
TypedStringValue nameHolder = new TypedStringValue(name);
209202
nameHolder.setSource(parserContext.extractSource(opElement));
210-
CacheOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
203+
CachePutOperation op = prop.merge(opElement, parserContext.getReaderContext(), new CachePutOperation());
204+
op.setUnless(getAttributeValue(opElement, "unless", ""));
211205

212206
Collection<CacheOperation> col = cacheOpMap.get(nameHolder);
213207
if (col == null) {
@@ -222,4 +216,14 @@ private RootBeanDefinition parseDefinitionSource(Element definition, ParserConte
222216
attributeSourceDefinition.getPropertyValues().add("nameMap", cacheOpMap);
223217
return attributeSourceDefinition;
224218
}
219+
220+
221+
private static String getAttributeValue(Element element, String attributeName, String defaultValue) {
222+
String attribute = element.getAttribute(attributeName);
223+
if(StringUtils.hasText(attribute)) {
224+
return attribute.trim();
225+
}
226+
return defaultValue;
227+
}
228+
225229
}

0 commit comments

Comments
 (0)