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
This commit is contained in:
Phillip Webb 2013-06-28 13:14:23 +02:00
parent 60532cbd1e
commit bf4563e204
2 changed files with 41 additions and 20 deletions

View File

@ -48,14 +48,12 @@ public class MethodReference extends SpelNodeImpl {
private volatile CachedMethodExecutor cachedExecutor;
public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
super(pos, arguments);
this.name = methodName;
this.nullSafe = nullSafe;
}
public final String getName() {
return this.name;
}
@ -102,6 +100,9 @@ public class MethodReference extends SpelNodeImpl {
state.popActiveContextObject();
}
}
TypedValue activeContextObject = state.getActiveContextObject();
TypeDescriptor target = (activeContextObject == null ? null
: activeContextObject.getTypeDescriptor());
List<TypeDescriptor> argumentTypes = getTypes(arguments);
if (currentContext.getValue() == null) {
if (this.nullSafe) {
@ -113,7 +114,7 @@ public class MethodReference extends SpelNodeImpl {
}
}
MethodExecutor executorToUse = getCachedExecutor(argumentTypes);
MethodExecutor executorToUse = getCachedExecutor(target, argumentTypes);
if (executorToUse != null) {
try {
return executorToUse.execute(state.getEvaluationContext(),
@ -139,7 +140,7 @@ public class MethodReference extends SpelNodeImpl {
// either there was no accessor or it no longer existed
executorToUse = findAccessorForMethod(this.name, argumentTypes, state);
this.cachedExecutor = new CachedMethodExecutor(executorToUse, argumentTypes);
this.cachedExecutor = new CachedMethodExecutor(executorToUse, target, argumentTypes);
try {
return executorToUse.execute(state.getEvaluationContext(),
state.getActiveContextObject().getValue(), arguments);
@ -227,15 +228,15 @@ public class MethodReference extends SpelNodeImpl {
: contextObject.getClass()));
}
private MethodExecutor getCachedExecutor(List<TypeDescriptor> argumentTypes) {
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(argumentTypes)) {
private MethodExecutor getCachedExecutor(TypeDescriptor target,
List<TypeDescriptor> argumentTypes) {
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(target, argumentTypes)) {
this.cachedExecutor = null;
return null;
}
return this.cachedExecutor.get();
}
private class MethodValueRef implements ValueRef {
private final ExpressionState state;
@ -244,15 +245,19 @@ public class MethodReference extends SpelNodeImpl {
private final Object target;
private TypeDescriptor targetType;
private final Object[] arguments;
private List<TypeDescriptor> argumentTypes;
MethodValueRef(ExpressionState state, EvaluationContext evaluationContext, Object object, Object[] arguments) {
MethodValueRef(ExpressionState state, EvaluationContext evaluationContext,
Object object, Object[] arguments) {
this.state = state;
this.evaluationContext = evaluationContext;
this.target = object;
this.targetType = TypeDescriptor.valueOf(target.getClass());
this.arguments = arguments;
this.argumentTypes = getTypes(this.arguments);
}
@ -260,7 +265,8 @@ public class MethodReference extends SpelNodeImpl {
@Override
public TypedValue getValue() {
MethodExecutor executorToUse = getCachedExecutor(this.argumentTypes);
MethodExecutor executorToUse = getCachedExecutor(this.targetType,
this.argumentTypes);
if (executorToUse != null) {
try {
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
@ -285,7 +291,7 @@ public class MethodReference extends SpelNodeImpl {
// either there was no accessor or it no longer existed
executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext);
MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.argumentTypes);
MethodReference.this.cachedExecutor = new CachedMethodExecutor(executorToUse, this.targetType, this.argumentTypes);
try {
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
}
@ -310,23 +316,24 @@ public class MethodReference extends SpelNodeImpl {
}
}
private static class CachedMethodExecutor {
private final MethodExecutor methodExecutor;
private final TypeDescriptor target;
private final List<TypeDescriptor> argumentTypes;
public CachedMethodExecutor(MethodExecutor methodExecutor,
public CachedMethodExecutor(MethodExecutor methodExecutor, TypeDescriptor target,
List<TypeDescriptor> argumentTypes) {
this.methodExecutor = methodExecutor;
this.target = target;
this.argumentTypes = argumentTypes;
}
public boolean isSuitable(List<TypeDescriptor> argumentTypes) {
return (this.methodExecutor != null && this.argumentTypes.equals(argumentTypes));
public boolean isSuitable(TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
return (this.methodExecutor != null && this.target != null
&& this.target.equals(target) && this.argumentTypes.equals(argumentTypes));
}
public MethodExecutor get() {

View File

@ -45,8 +45,8 @@ public class CachedMethodExecutorTests {
@Test
public void testCachedExecution() throws Exception {
Expression expression = this.parser.parseExpression("echo(#something)");
public void testCachedExecutionForParameters() throws Exception {
Expression expression = this.parser.parseExpression("echo(#var)");
assertMethodExecution(expression, 42, "int: 42");
assertMethodExecution(expression, 42, "int: 42");
@ -54,18 +54,32 @@ public class CachedMethodExecutorTests {
assertMethodExecution(expression, 42, "int: 42");
}
@Test
public void testCachedExecutionForTarget() throws Exception {
Expression expression = this.parser.parseExpression("#var.echo(42)");
assertMethodExecution(expression, new RootObject(), "int: 42");
assertMethodExecution(expression, new RootObject(), "int: 42");
assertMethodExecution(expression, new BaseObject(), "String: 42");
assertMethodExecution(expression, new RootObject(), "int: 42");
}
private void assertMethodExecution(Expression expression, Object var, String expected) {
this.context.setVariable("something", var);
this.context.setVariable("var", var);
assertEquals(expected, expression.getValue(this.context));
}
public static class RootObject {
public static class BaseObject {
public String echo(String value) {
return "String: " + value;
}
}
public static class RootObject extends BaseObject {
public String echo(int value) {
return "int: " + value;
}