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:
parent
60532cbd1e
commit
bf4563e204
|
@ -48,14 +48,12 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
|
|
||||||
private volatile CachedMethodExecutor cachedExecutor;
|
private volatile CachedMethodExecutor cachedExecutor;
|
||||||
|
|
||||||
|
|
||||||
public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
|
public MethodReference(boolean nullSafe, String methodName, int pos, SpelNodeImpl... arguments) {
|
||||||
super(pos, arguments);
|
super(pos, arguments);
|
||||||
this.name = methodName;
|
this.name = methodName;
|
||||||
this.nullSafe = nullSafe;
|
this.nullSafe = nullSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public final String getName() {
|
public final String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
@ -102,6 +100,9 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
state.popActiveContextObject();
|
state.popActiveContextObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TypedValue activeContextObject = state.getActiveContextObject();
|
||||||
|
TypeDescriptor target = (activeContextObject == null ? null
|
||||||
|
: activeContextObject.getTypeDescriptor());
|
||||||
List<TypeDescriptor> argumentTypes = getTypes(arguments);
|
List<TypeDescriptor> argumentTypes = getTypes(arguments);
|
||||||
if (currentContext.getValue() == null) {
|
if (currentContext.getValue() == null) {
|
||||||
if (this.nullSafe) {
|
if (this.nullSafe) {
|
||||||
|
@ -113,7 +114,7 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MethodExecutor executorToUse = getCachedExecutor(argumentTypes);
|
MethodExecutor executorToUse = getCachedExecutor(target, argumentTypes);
|
||||||
if (executorToUse != null) {
|
if (executorToUse != null) {
|
||||||
try {
|
try {
|
||||||
return executorToUse.execute(state.getEvaluationContext(),
|
return executorToUse.execute(state.getEvaluationContext(),
|
||||||
|
@ -139,7 +140,7 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
|
|
||||||
// either there was no accessor or it no longer existed
|
// either there was no accessor or it no longer existed
|
||||||
executorToUse = findAccessorForMethod(this.name, argumentTypes, state);
|
executorToUse = findAccessorForMethod(this.name, argumentTypes, state);
|
||||||
this.cachedExecutor = new CachedMethodExecutor(executorToUse, argumentTypes);
|
this.cachedExecutor = new CachedMethodExecutor(executorToUse, target, argumentTypes);
|
||||||
try {
|
try {
|
||||||
return executorToUse.execute(state.getEvaluationContext(),
|
return executorToUse.execute(state.getEvaluationContext(),
|
||||||
state.getActiveContextObject().getValue(), arguments);
|
state.getActiveContextObject().getValue(), arguments);
|
||||||
|
@ -227,15 +228,15 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
: contextObject.getClass()));
|
: contextObject.getClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodExecutor getCachedExecutor(List<TypeDescriptor> argumentTypes) {
|
private MethodExecutor getCachedExecutor(TypeDescriptor target,
|
||||||
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(argumentTypes)) {
|
List<TypeDescriptor> argumentTypes) {
|
||||||
|
if (this.cachedExecutor == null || !this.cachedExecutor.isSuitable(target, argumentTypes)) {
|
||||||
this.cachedExecutor = null;
|
this.cachedExecutor = null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.cachedExecutor.get();
|
return this.cachedExecutor.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class MethodValueRef implements ValueRef {
|
private class MethodValueRef implements ValueRef {
|
||||||
|
|
||||||
private final ExpressionState state;
|
private final ExpressionState state;
|
||||||
|
@ -244,15 +245,19 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
|
|
||||||
private final Object target;
|
private final Object target;
|
||||||
|
|
||||||
|
private TypeDescriptor targetType;
|
||||||
|
|
||||||
private final Object[] arguments;
|
private final Object[] arguments;
|
||||||
|
|
||||||
private List<TypeDescriptor> argumentTypes;
|
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.state = state;
|
||||||
this.evaluationContext = evaluationContext;
|
this.evaluationContext = evaluationContext;
|
||||||
this.target = object;
|
this.target = object;
|
||||||
|
this.targetType = TypeDescriptor.valueOf(target.getClass());
|
||||||
this.arguments = arguments;
|
this.arguments = arguments;
|
||||||
this.argumentTypes = getTypes(this.arguments);
|
this.argumentTypes = getTypes(this.arguments);
|
||||||
}
|
}
|
||||||
|
@ -260,7 +265,8 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TypedValue getValue() {
|
public TypedValue getValue() {
|
||||||
MethodExecutor executorToUse = getCachedExecutor(this.argumentTypes);
|
MethodExecutor executorToUse = getCachedExecutor(this.targetType,
|
||||||
|
this.argumentTypes);
|
||||||
if (executorToUse != null) {
|
if (executorToUse != null) {
|
||||||
try {
|
try {
|
||||||
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
|
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
|
// either there was no accessor or it no longer existed
|
||||||
executorToUse = findAccessorForMethod(MethodReference.this.name, argumentTypes, this.target, this.evaluationContext);
|
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 {
|
try {
|
||||||
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
|
return executorToUse.execute(this.evaluationContext, this.target, this.arguments);
|
||||||
}
|
}
|
||||||
|
@ -310,23 +316,24 @@ public class MethodReference extends SpelNodeImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class CachedMethodExecutor {
|
private static class CachedMethodExecutor {
|
||||||
|
|
||||||
private final MethodExecutor methodExecutor;
|
private final MethodExecutor methodExecutor;
|
||||||
|
|
||||||
|
private final TypeDescriptor target;
|
||||||
|
|
||||||
private final List<TypeDescriptor> argumentTypes;
|
private final List<TypeDescriptor> argumentTypes;
|
||||||
|
|
||||||
|
public CachedMethodExecutor(MethodExecutor methodExecutor, TypeDescriptor target,
|
||||||
public CachedMethodExecutor(MethodExecutor methodExecutor,
|
|
||||||
List<TypeDescriptor> argumentTypes) {
|
List<TypeDescriptor> argumentTypes) {
|
||||||
this.methodExecutor = methodExecutor;
|
this.methodExecutor = methodExecutor;
|
||||||
|
this.target = target;
|
||||||
this.argumentTypes = argumentTypes;
|
this.argumentTypes = argumentTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSuitable(TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
|
||||||
public boolean isSuitable(List<TypeDescriptor> argumentTypes) {
|
return (this.methodExecutor != null && this.target != null
|
||||||
return (this.methodExecutor != null && this.argumentTypes.equals(argumentTypes));
|
&& this.target.equals(target) && this.argumentTypes.equals(argumentTypes));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodExecutor get() {
|
public MethodExecutor get() {
|
||||||
|
|
|
@ -45,8 +45,8 @@ public class CachedMethodExecutorTests {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCachedExecution() throws Exception {
|
public void testCachedExecutionForParameters() throws Exception {
|
||||||
Expression expression = this.parser.parseExpression("echo(#something)");
|
Expression expression = this.parser.parseExpression("echo(#var)");
|
||||||
|
|
||||||
assertMethodExecution(expression, 42, "int: 42");
|
assertMethodExecution(expression, 42, "int: 42");
|
||||||
assertMethodExecution(expression, 42, "int: 42");
|
assertMethodExecution(expression, 42, "int: 42");
|
||||||
|
@ -54,18 +54,32 @@ public class CachedMethodExecutorTests {
|
||||||
assertMethodExecution(expression, 42, "int: 42");
|
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) {
|
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));
|
assertEquals(expected, expression.getValue(this.context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class RootObject {
|
public static class BaseObject {
|
||||||
|
|
||||||
public String echo(String value) {
|
public String echo(String value) {
|
||||||
return "String: " + value;
|
return "String: " + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RootObject extends BaseObject {
|
||||||
|
|
||||||
public String echo(int value) {
|
public String echo(int value) {
|
||||||
return "int: " + value;
|
return "int: " + value;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue