Skip to content

Commit 2d8e0c8

Browse files
committed
Fix expression cache
Prior to this commit, only the java.lang.reflect.Method was used to identify an annotated method. As a result, if different annotations were placed on different methods (method overriding, interface implementation) only the first one (cached) was used. LazyParamAwareEvaluationContext was affected by the exact same problem and has been also fixed. Issue: SPR-11692 (cherry picked from commit df34bab)
1 parent 03ae8ee commit 2d8e0c8

File tree

6 files changed

+249
-39
lines changed

6 files changed

+249
-39
lines changed

spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ protected class CacheOperationContext {
372372

373373
private final Collection<? extends Cache> caches;
374374

375+
private final MethodCacheKey methodCacheKey;
376+
375377
public CacheOperationContext(CacheOperation operation, Method method,
376378
Object[] args, Object target, Class<?> targetClass) {
377379
this.operation = operation;
@@ -380,6 +382,7 @@ public CacheOperationContext(CacheOperation operation, Method method,
380382
this.target = target;
381383
this.targetClass = targetClass;
382384
this.caches = CacheAspectSupport.this.getCaches(operation);
385+
this.methodCacheKey = new MethodCacheKey(method, targetClass);
383386
}
384387

385388
private Object[] extractArgs(Method method, Object[] args) {
@@ -396,7 +399,7 @@ private Object[] extractArgs(Method method, Object[] args) {
396399
protected boolean isConditionPassing(Object result) {
397400
if (StringUtils.hasText(this.operation.getCondition())) {
398401
EvaluationContext evaluationContext = createEvaluationContext(result);
399-
return evaluator.condition(this.operation.getCondition(), this.method, evaluationContext);
402+
return evaluator.condition(this.operation.getCondition(), this.methodCacheKey, evaluationContext);
400403
}
401404
return true;
402405
}
@@ -411,7 +414,7 @@ else if (this.operation instanceof CachePutOperation) {
411414
}
412415
if (StringUtils.hasText(unless)) {
413416
EvaluationContext evaluationContext = createEvaluationContext(value);
414-
return !evaluator.unless(unless, this.method, evaluationContext);
417+
return !evaluator.unless(unless, this.methodCacheKey, evaluationContext);
415418
}
416419
return true;
417420
}
@@ -423,7 +426,7 @@ else if (this.operation instanceof CachePutOperation) {
423426
protected Object generateKey(Object result) {
424427
if (StringUtils.hasText(this.operation.getKey())) {
425428
EvaluationContext evaluationContext = createEvaluationContext(result);
426-
return evaluator.key(this.operation.getKey(), this.method, evaluationContext);
429+
return evaluator.key(this.operation.getKey(), this.methodCacheKey, evaluationContext);
427430
}
428431
return keyGenerator.generate(this.target, this.method, this.args);
429432
}

spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -27,16 +27,19 @@
2727
import org.springframework.expression.EvaluationContext;
2828
import org.springframework.expression.Expression;
2929
import org.springframework.expression.spel.standard.SpelExpressionParser;
30+
import org.springframework.util.ObjectUtils;
3031

3132
/**
3233
* Utility class handling the SpEL expression parsing.
3334
* Meant to be used as a reusable, thread-safe component.
3435
*
35-
* <p>Performs internal caching for performance reasons.
36+
* <p>Performs internal caching for performance reasons
37+
* using {@link MethodCacheKey}.
3638
*
3739
* @author Costin Leau
3840
* @author Phillip Webb
3941
* @author Sam Brannen
42+
* @author Stephane Nicoll
4043
* @since 3.1
4144
*/
4245
class ExpressionEvaluator {
@@ -49,13 +52,16 @@ class ExpressionEvaluator {
4952
// shared param discoverer since it caches data internally
5053
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
5154

52-
private final Map<String, Expression> keyCache = new ConcurrentHashMap<String, Expression>(64);
55+
private final Map<ExpressionKey, Expression> keyCache
56+
= new ConcurrentHashMap<ExpressionKey, Expression>(64);
5357

54-
private final Map<String, Expression> conditionCache = new ConcurrentHashMap<String, Expression>(64);
58+
private final Map<ExpressionKey, Expression> conditionCache
59+
= new ConcurrentHashMap<ExpressionKey, Expression>(64);
5560

56-
private final Map<String, Expression> unlessCache = new ConcurrentHashMap<String, Expression>(64);
61+
private final Map<ExpressionKey, Expression> unlessCache
62+
= new ConcurrentHashMap<ExpressionKey, Expression>(64);
5763

58-
private final Map<String, Method> targetMethodCache = new ConcurrentHashMap<String, Method>(64);
64+
private final Map<MethodCacheKey, Method> targetMethodCache = new ConcurrentHashMap<MethodCacheKey, Method>(64);
5965

6066

6167
/**
@@ -93,22 +99,22 @@ public EvaluationContext createEvaluationContext(Collection<? extends Cache> cac
9399
return evaluationContext;
94100
}
95101

96-
public Object key(String keyExpression, Method method, EvaluationContext evalContext) {
97-
return getExpression(this.keyCache, keyExpression, method).getValue(evalContext);
102+
public Object key(String keyExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
103+
return getExpression(this.keyCache, keyExpression, methodKey).getValue(evalContext);
98104
}
99105

100-
public boolean condition(String conditionExpression, Method method, EvaluationContext evalContext) {
101-
return getExpression(this.conditionCache, conditionExpression, method).getValue(
106+
public boolean condition(String conditionExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
107+
return getExpression(this.conditionCache, conditionExpression, methodKey).getValue(
102108
evalContext, boolean.class);
103109
}
104110

105-
public boolean unless(String unlessExpression, Method method, EvaluationContext evalContext) {
106-
return getExpression(this.unlessCache, unlessExpression, method).getValue(
111+
public boolean unless(String unlessExpression, MethodCacheKey methodKey, EvaluationContext evalContext) {
112+
return getExpression(this.unlessCache, unlessExpression, methodKey).getValue(
107113
evalContext, boolean.class);
108114
}
109115

110-
private Expression getExpression(Map<String, Expression> cache, String expression, Method method) {
111-
String key = toString(method, expression);
116+
private Expression getExpression(Map<ExpressionKey, Expression> cache, String expression, MethodCacheKey methodKey) {
117+
ExpressionKey key = createKey(methodKey, expression);
112118
Expression rtn = cache.get(key);
113119
if (rtn == null) {
114120
rtn = this.parser.parseExpression(expression);
@@ -117,13 +123,37 @@ private Expression getExpression(Map<String, Expression> cache, String expressio
117123
return rtn;
118124
}
119125

120-
private String toString(Method method, String expression) {
121-
StringBuilder sb = new StringBuilder();
122-
sb.append(method.getDeclaringClass().getName());
123-
sb.append("#");
124-
sb.append(method.toString());
125-
sb.append("#");
126-
sb.append(expression);
127-
return sb.toString();
126+
private ExpressionKey createKey(MethodCacheKey methodCacheKey, String expression) {
127+
return new ExpressionKey(methodCacheKey, expression);
128128
}
129+
130+
131+
private static class ExpressionKey {
132+
private final MethodCacheKey methodCacheKey;
133+
private final String expression;
134+
135+
private ExpressionKey(MethodCacheKey methodCacheKey, String expression) {
136+
this.methodCacheKey = methodCacheKey;
137+
this.expression = expression;
138+
}
139+
140+
@Override
141+
public boolean equals(Object other) {
142+
if (this == other) {
143+
return true;
144+
}
145+
if (!(other instanceof ExpressionKey)) {
146+
return false;
147+
}
148+
ExpressionKey otherKey = (ExpressionKey) other;
149+
return (this.methodCacheKey.equals(otherKey.methodCacheKey)
150+
&& ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
151+
}
152+
153+
@Override
154+
public int hashCode() {
155+
return this.methodCacheKey.hashCode() * 29 + (this.expression != null ? this.expression.hashCode() : 0);
156+
}
157+
}
158+
129159
}

spring-context/src/main/java/org/springframework/cache/interceptor/LazyParamAwareEvaluationContext.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -33,6 +33,7 @@
3333
* (rather then a dedicated 'closure'-like class for deferred execution).
3434
*
3535
* @author Costin Leau
36+
* @author Stephane Nicoll
3637
* @since 3.1
3738
*/
3839
class LazyParamAwareEvaluationContext extends StandardEvaluationContext {
@@ -45,13 +46,13 @@ class LazyParamAwareEvaluationContext extends StandardEvaluationContext {
4546

4647
private final Class<?> targetClass;
4748

48-
private final Map<String, Method> methodCache;
49+
private final Map<MethodCacheKey, Method> methodCache;
4950

5051
private boolean paramLoaded = false;
5152

5253

5354
LazyParamAwareEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
54-
Object[] args, Class<?> targetClass, Map<String, Method> methodCache) {
55+
Object[] args, Class<?> targetClass, Map<MethodCacheKey, Method> methodCache) {
5556
super(rootObject);
5657

5758
this.paramDiscoverer = paramDiscoverer;
@@ -85,7 +86,7 @@ private void loadArgsAsVariables() {
8586
return;
8687
}
8788

88-
String methodKey = toString(this.method);
89+
MethodCacheKey methodKey = new MethodCacheKey(this.method, this.targetClass);
8990
Method targetMethod = this.methodCache.get(methodKey);
9091
if (targetMethod == null) {
9192
targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass);
@@ -110,11 +111,4 @@ private void loadArgsAsVariables() {
110111
}
111112
}
112113

113-
private String toString(Method m) {
114-
StringBuilder sb = new StringBuilder();
115-
sb.append(m.getDeclaringClass().getName());
116-
sb.append("#");
117-
sb.append(m.toString());
118-
return sb.toString();
119-
}
120114
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cache.interceptor;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.springframework.util.Assert;
22+
import org.springframework.util.ObjectUtils;
23+
24+
/**
25+
* Represent a method on a particular {@link Class} and is suitable as a key.
26+
* <p>Mainly for internal use within the framework.
27+
*
28+
* @author Costin Leau
29+
* @author Stephane Nicoll
30+
* @since 4.0.4
31+
*/
32+
public final class MethodCacheKey {
33+
34+
private final Method method;
35+
36+
private final Class<?> targetClass;
37+
38+
public MethodCacheKey(Method method, Class<?> targetClass) {
39+
Assert.notNull(method, "method must be set.");
40+
this.method = method;
41+
this.targetClass = targetClass;
42+
}
43+
44+
@Override
45+
public boolean equals(Object other) {
46+
if (this == other) {
47+
return true;
48+
}
49+
if (!(other instanceof MethodCacheKey)) {
50+
return false;
51+
}
52+
MethodCacheKey otherKey = (MethodCacheKey) other;
53+
return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass,
54+
otherKey.targetClass));
55+
}
56+
57+
@Override
58+
public int hashCode() {
59+
return this.method.hashCode() * 29 + (this.targetClass != null ? this.targetClass.hashCode() : 0);
60+
}
61+
62+
}

0 commit comments

Comments
 (0)