Polish SpEL's VariableReference

This commit is contained in:
Sam Brannen
2024-03-02 17:38:31 +01:00
parent 11c40b5c1c
commit 5cba32df32

View File

@@ -29,7 +29,8 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.lang.Nullable;
/**
* Represents a variable reference — for example, {@code #someVar}.
* Represents a variable reference — for example, {@code #root}, {@code #this},
* {@code #someVar}, etc.
*
* @author Andy Clement
* @author Sam Brannen
@@ -37,10 +38,11 @@ import org.springframework.lang.Nullable;
*/
public class VariableReference extends SpelNodeImpl {
// Well known variables:
private static final String THIS = "this"; // currently active context object
/** Currently active context object. */
private static final String THIS = "this";
private static final String ROOT = "root"; // root context object
/** Root context object. */
private static final String ROOT = "root";
private final String name;
@@ -54,41 +56,56 @@ public class VariableReference extends SpelNodeImpl {
@Override
public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {
if (THIS.equals(this.name)) {
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(), this);
}
if (this.name.equals(ROOT)) {
if (ROOT.equals(this.name)) {
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(), this);
}
TypedValue result = state.lookupVariable(this.name);
// a null value will mean either the value was null or the variable was not found
// A null value in the returned VariableRef will mean either the value was
// null or the variable was not found.
return new VariableRef(this.name, result, state.getEvaluationContext());
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws SpelEvaluationException {
if (this.name.equals(THIS)) {
if (THIS.equals(this.name)) {
return state.getActiveContextObject();
}
if (this.name.equals(ROOT)) {
if (ROOT.equals(this.name)) {
TypedValue result = state.getRootContextObject();
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(result.getValue());
return result;
}
TypedValue result = state.lookupVariable(this.name);
Object value = result.getValue();
setExitTypeDescriptor(result.getValue());
// A null value in the returned TypedValue will mean either the value was
// null or the variable was not found.
return result;
}
/**
* Set the exit type descriptor for the supplied value.
* <p>If the value is {@code null}, we set the exit type descriptor to
* {@link Object}.
* <p>If the value's type is not public, {@link #generateCode} would insert
* a checkcast to the non-public type in the generated byte code which would
* result in an {@link IllegalAccessError} when the compiled byte code is
* invoked. Thus, as a preventative measure, we set the exit type descriptor
* to {@code Object} in such cases. If resorting to {@code Object} is not
* sufficient, we could consider traversing the hierarchy to find the first
* public type.
*/
private void setExitTypeDescriptor(@Nullable Object value) {
if (value == null || !Modifier.isPublic(value.getClass().getModifiers())) {
// If the type is not public then when generateCode produces a checkcast to it
// then an IllegalAccessError will occur.
// If resorting to Object isn't sufficient, the hierarchy could be traversed for
// the first public type.
this.exitTypeDescriptor = "Ljava/lang/Object";
}
else {
this.exitTypeDescriptor = CodeFlow.toDescriptorFromObject(value);
}
// a null value will mean either the value was null or the variable was not found
return result;
}
@Override
@@ -105,7 +122,7 @@ public class VariableReference extends SpelNodeImpl {
@Override
public boolean isWritable(ExpressionState expressionState) throws SpelEvaluationException {
return !(this.name.equals(THIS) || this.name.equals(ROOT));
return !(THIS.equals(this.name) || ROOT.equals(this.name));
}
@Override
@@ -115,13 +132,14 @@ public class VariableReference extends SpelNodeImpl {
@Override
public void generateCode(MethodVisitor mv, CodeFlow cf) {
if (this.name.equals(ROOT)) {
mv.visitVarInsn(ALOAD,1);
if (ROOT.equals(this.name)) {
mv.visitVarInsn(ALOAD, 1);
}
else {
mv.visitVarInsn(ALOAD, 2);
mv.visitLdcInsn(this.name);
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext", "lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;",true);
mv.visitMethodInsn(INVOKEINTERFACE, "org/springframework/expression/EvaluationContext",
"lookupVariable", "(Ljava/lang/String;)Ljava/lang/Object;", true);
}
CodeFlow.insertCheckCast(mv, this.exitTypeDescriptor);
cf.pushDescriptor(this.exitTypeDescriptor);