FunctionReference's method field is volatile

Issue: SPR-16255
This commit is contained in:
Juergen Hoeller 2018-01-07 23:23:45 +01:00
parent 0a06bce3a6
commit 6a1fe0b1d0
1 changed files with 28 additions and 20 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -47,6 +47,7 @@ import org.springframework.util.ReflectionUtils;
* (right now), so the names must be unique. * (right now), so the names must be unique.
* *
* @author Andy Clement * @author Andy Clement
* @author Juergen Hoeller
* @since 3.0 * @since 3.0
*/ */
public class FunctionReference extends SpelNodeImpl { public class FunctionReference extends SpelNodeImpl {
@ -56,9 +57,7 @@ public class FunctionReference extends SpelNodeImpl {
// Captures the most recently used method for the function invocation *if* the method // Captures the most recently used method for the function invocation *if* the method
// can safely be used for compilation (i.e. no argument conversion is going on) // can safely be used for compilation (i.e. no argument conversion is going on)
@Nullable @Nullable
private Method method; private volatile Method method;
private boolean argumentConversionOccurred;
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) { public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
@ -90,14 +89,13 @@ public class FunctionReference extends SpelNodeImpl {
} }
/** /**
* Execute a function represented as a java.lang.reflect.Method. * Execute a function represented as a {@code java.lang.reflect.Method}.
* @param state the expression evaluation state * @param state the expression evaluation state
* @param method the method to invoke * @param method the method to invoke
* @return the return value of the invoked Java method * @return the return value of the invoked Java method
* @throws EvaluationException if there is any problem invoking the method * @throws EvaluationException if there is any problem invoking the method
*/ */
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException { private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
this.method = null;
Object[] functionArgs = getArguments(state); Object[] functionArgs = getArguments(state);
if (!method.isVarArgs() && method.getParameterCount() != functionArgs.length) { if (!method.isVarArgs() && method.getParameterCount() != functionArgs.length) {
@ -112,25 +110,33 @@ public class FunctionReference extends SpelNodeImpl {
// Convert arguments if necessary and remap them for varargs if required // Convert arguments if necessary and remap them for varargs if required
TypeConverter converter = state.getEvaluationContext().getTypeConverter(); TypeConverter converter = state.getEvaluationContext().getTypeConverter();
argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method); boolean argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method);
if (method.isVarArgs()) { if (method.isVarArgs()) {
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation( functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
method.getParameterTypes(), functionArgs); method.getParameterTypes(), functionArgs);
} }
boolean compilable = false;
try { try {
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
Object result = method.invoke(method.getClass(), functionArgs); Object result = method.invoke(method.getClass(), functionArgs);
if (!argumentConversionOccurred) { compilable = !argumentConversionOccurred;
this.method = method;
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
}
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method, -1)).narrow(result)); return new TypedValue(result, new TypeDescriptor(new MethodParameter(method, -1)).narrow(result));
} }
catch (Exception ex) { catch (Exception ex) {
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL, throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
this.name, ex.getMessage()); this.name, ex.getMessage());
} }
finally {
if (compilable) {
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
this.method = method;
}
else {
this.exitTypeDescriptor = null;
this.method = null;
}
}
} }
@Override @Override
@ -162,12 +168,13 @@ public class FunctionReference extends SpelNodeImpl {
@Override @Override
public boolean isCompilable() { public boolean isCompilable() {
if (this.method == null || this.argumentConversionOccurred) { Method method = this.method;
if (method == null) {
return false; return false;
} }
int methodModifiers = this.method.getModifiers(); int methodModifiers = method.getModifiers();
if (!Modifier.isStatic(methodModifiers) || !Modifier.isPublic(methodModifiers) || if (!Modifier.isStatic(methodModifiers) || !Modifier.isPublic(methodModifiers) ||
!Modifier.isPublic(this.method.getDeclaringClass().getModifiers())) { !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
return false; return false;
} }
for (SpelNodeImpl child : this.children) { for (SpelNodeImpl child : this.children) {
@ -180,11 +187,12 @@ public class FunctionReference extends SpelNodeImpl {
@Override @Override
public void generateCode(MethodVisitor mv, CodeFlow cf) { public void generateCode(MethodVisitor mv, CodeFlow cf) {
Assert.state(this.method != null, "No method handle"); Method method = this.method;
String classDesc = this.method.getDeclaringClass().getName().replace('.', '/'); Assert.state(method != null, "No method handle");
generateCodeForArguments(mv, cf, this.method, this.children); String classDesc = method.getDeclaringClass().getName().replace('.', '/');
mv.visitMethodInsn(INVOKESTATIC, classDesc, this.method.getName(), generateCodeForArguments(mv, cf, method, this.children);
CodeFlow.createSignatureDescriptor(this.method), false); mv.visitMethodInsn(INVOKESTATIC, classDesc, method.getName(),
CodeFlow.createSignatureDescriptor(method), false);
cf.pushDescriptor(this.exitTypeDescriptor); cf.pushDescriptor(this.exitTypeDescriptor);
} }