Skip to content

Commit bf4563e

Browse files
committed
Include target types in MethodReference cache
Update the cached MethodExecutor in MethodReference to include the target type. Prevents the incorrect use of the cache when the SpEL expression refers to a different target object. Issue: SPR-10657
1 parent 60532cb commit bf4563e

File tree

2 files changed

+41
-20
lines changed

2 files changed

+41
-20
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/MethodReference.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,12 @@ public class MethodReference extends SpelNodeImpl {
4848

4949
private volatile CachedMethodExecutor cachedExecutor;
5050

51-
5251
public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
5352
super(pos, arguments);
5453
this.name = methodName;
5554
this.nullSafe = nullSafe;
5655
}
5756

58-
5957
public final String getName() {
6058
return this.name;
6159
}
@@ -102,6 +100,9 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
102100
state.popActiveContextObject();
103101
}
104102
}
103+
TypedValue activeContextObject = state.getActiveContextObject();
104+
TypeDescriptor target = (activeContextObject == null ? null
105+
: activeContextObject.getTypeDescriptor());
105106
List<TypeDescriptor> argumentTypes = getTypes(arguments);
106107
if (currentContext.getValue() == null) {
107108
if (this.nullSafe) {
@@ -113,7 +114,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
113114
}
114115
}
115116

116-
MethodExecutor executorToUse = getCachedExecutor(argumentTypes);
117+
MethodExecutor executorToUse = getCachedExecutor(target, argumentTypes);
117118
if (executorToUse != null) {
118119
try {
119120
return executorToUse.execute(state.getEvaluationContext(),
@@ -139,7 +140,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
139140

140141
// either there was no accessor or it no longer existed
141142
executorToUse = findAccessorForMethod(this.name, argumentTypes, state);
142-
this.cachedExecutor = new CachedMethodExecutor(executorToUse, argumentTypes);
143+
this.cachedExecutor = new CachedMethodExecutor(executorToUse, target, argumentTypes);
143144
try {
144145
return executorToUse.execute(state.getEvaluationContext(),
145146
state.getActiveContextObject().getValue(), arguments);
@@ -227,15 +228,15 @@ private MethodExecutor findAccessorForMethod(String name,
227228
: contextObject.getClass()));
228229
}
229230

230-
private MethodExecutor getCachedExecutor(List<TypeDescriptor> argumentTypes) {
231-
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(argumentTypes)) {
231+
private MethodExecutor getCachedExecutor(TypeDescriptor target,
232+
List<TypeDescriptor> argumentTypes) {
233+
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(target, argumentTypes)) {
232234
this.cachedExecutor = null;
233235
return null;
234236
}
235237
return this.cachedExecutor.get();
236238
}
237239

238-
239240
private class MethodValueRef implements ValueRef {
240241

241242
private final ExpressionState state;
@@ -244,23 +245,28 @@ private class MethodValueRef implements ValueRef {
244245

245246
private final Object target;
246247

248+
private TypeDescriptor targetType;
249+
247250
private final Object[] arguments;
248251

249252
private List<TypeDescriptor> argumentTypes;
250253

251254

252-
MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) {
255+
MethodValueRef(ExpressionState state, EvaluationContext evaluationContext,
256+
Object object, Object[] arguments) {
253257
this.state = state;
254258
this.evaluationContext = evaluationContext;
255259
this.target = object;
260+
this.targetType = TypeDescriptor.valueOf(target.getClass());
256261
this.arguments = arguments;
257262
this.argumentTypes = getTypes(this.arguments);
258263
}
259264

260265

261266
@Override
262267
public TypedValue getValue() {
263-
MethodExecutor executorToUse = getCachedExecutor(this.argumentTypes);
268+
MethodExecutor executorToUse = getCachedExecutor(this.targetType,
269+
this.argumentTypes);
264270
if (executorToUse != null) {
265271
try {
266272
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
@@ -285,7 +291,7 @@ public TypedValue getValue() {
285291

286292
// either there was no accessor or it no longer existed
287293
executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext);
288-
MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.argumentTypes);
294+
MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.targetType, this.argumentTypes);
289295
try {
290296
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
291297
}
@@ -310,23 +316,24 @@ public boolean isWritable() {
310316
}
311317
}
312318

313-
314319
private static class CachedMethodExecutor {
315320

316321
private final MethodExecutor methodExecutor;
317322

318-
private final List<TypeDescriptor> argumentTypes;
323+
private final TypeDescriptor target;
319324

325+
private final List<TypeDescriptor> argumentTypes;
320326

321-
public CachedMethodExecutor(MethodExecutor methodExecutor,
327+
public CachedMethodExecutor(MethodExecutor methodExecutor, TypeDescriptor target,
322328
List<TypeDescriptor> argumentTypes) {
323329
this.methodExecutor = methodExecutor;
330+
this.target = target;
324331
this.argumentTypes = argumentTypes;
325332
}
326333

327-
328-
public boolean isSuitable(List<TypeDescriptor> argumentTypes) {
329-
return (this.methodExecutor != null && this.argumentTypes.equals(argumentTypes));
334+
public boolean isSuitable(TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
335+
return (this.methodExecutor != null && this.target != null
336+
&& this.target.equals(target) && this.argumentTypes.equals(argumentTypes));
330337
}
331338

332339
public MethodExecutor get() {

spring-expression/src/test/java/org/springframework/expression/spel/CachedMethodExecutorTests.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,41 @@ public void setUp() throws Exception {
4545

4646

4747
@Test
48-
public void testCachedExecution() throws Exception {
49-
Expression expression = this.parser.parseExpression("echo(#something)");
48+
public void testCachedExecutionForParameters() throws Exception {
49+
Expression expression = this.parser.parseExpression("echo(#var)");
5050

5151
assertMethodExecution(expression, 42, "int: 42");
5252
assertMethodExecution(expression, 42, "int: 42");
5353
assertMethodExecution(expression, "Deep Thought", "String: Deep Thought");
5454
assertMethodExecution(expression, 42, "int: 42");
5555
}
5656

57+
@Test
58+
public void testCachedExecutionForTarget() throws Exception {
59+
Expression expression = this.parser.parseExpression("#var.echo(42)");
60+
61+
assertMethodExecution(expression, new RootObject(), "int: 42");
62+
assertMethodExecution(expression, new RootObject(), "int: 42");
63+
assertMethodExecution(expression, new BaseObject(), "String: 42");
64+
assertMethodExecution(expression, new RootObject(), "int: 42");
65+
}
66+
5767
private void assertMethodExecution(Expression expression, Object var, String expected) {
58-
this.context.setVariable("something", var);
68+
this.context.setVariable("var", var);
5969
assertEquals(expected, expression.getValue(this.context));
6070
}
6171

6272

63-
public static class RootObject {
73+
public static class BaseObject {
6474

6575
public String echo(String value) {
6676
return "String: " + value;
6777
}
6878

79+
}
80+
81+
public static class RootObject extends BaseObject {
82+
6983
public String echo(int value) {
7084
return "int: " + value;
7185
}

0 commit comments

Comments
 (0)