In my application, I have objects with several SpEL
expressions that usually contains signatures of methods with boolean
return type to invoke and logical operators. Before these objects are cached, I check the consistency of the expression by simply executing the parsed expression. When an exception is thrown, I set an appropriate flag inside the object to indicate that the expression is invalid to avoid further execution.
I am executing the expression on an EvaluationContext
that implements all methods that are permitted to be a part of the expression. All these methods return false
. I have come across a problem that involves a short circuit evaluation.
Given methodOne
and methodTwo
are the only permitted methods to invoke, this expression correctly sets the inconsistency flag
methodERROROne("arg") AND methodTwo("arg")
this one, however, does not because methodOne
returns false
, Spring
uses short circuit evaluation and does not execute the remaining operands. This causes the expression to fail when it is executed on real EvaluationContext
and the methodOne
returns true
methodOne("arg") AND methodERRORTwo("arg")
Is there a way to disable short circuit evaluation is Spring expression language?
No; the OpAnd
operator always short-circuits...
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
if (!getBooleanValue(state, getLeftOperand())) {
// no need to evaluate right operand
return BooleanTypedValue.FALSE;
}
return BooleanTypedValue.forValue(getBooleanValue(state, getRightOperand()));
}
...there is no equivalent to Java's &
operator.
EDIT
All these methods return false
If they all return false
can't you use "!(!m1() and !m2())"
?
or even
"!((!m1() or m1()) and (!m2() or m2()))"
If you really want to disable short circuit evaluation it is possible, but you have to get your hands dirty. As Gary pointed out logic in OpAnd (OpOr) is fixed. So we have to change/override implementation of OpAnd/OpOr.getValueInternal(ExpressionState state). This requies some source code copying, so source code can be found here . My samples may differ a bit since I implemented it and tested on different version of maven dependency:
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
First of all, we have to implement our own version of OpAnd and OpOr. Since OpAnd.getBooleanValue(ExpressionState state, SpelNodeImpl operand) and SpelNodeImpl.getValue(ExpressionState state, Class desiredReturnType) are private, we have to implement our version of getBooleanValue.
import org.springframework.expression.EvaluationException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.ast.OpAnd;
import org.springframework.expression.spel.ast.SpelNodeImpl;
import org.springframework.expression.spel.support.BooleanTypedValue;
import org.springframework.lang.Nullable;
public class DomailOpAnd extends OpAnd {
public static boolean getBooleanValue(ExpressionState state, SpelNodeImpl operand) {
try {
Boolean value = ExpressionUtils.convertTypedValue(state.getEvaluationContext(), operand.getValueInternal(state), Boolean.class);
//Boolean value = operand.getValue(state, Boolean.class);
assertValueNotNull(value);
return value;
}
catch (SpelEvaluationException ex) {
ex.setPosition(operand.getStartPosition());
throw ex;
}
}
private static void assertValueNotNull(@Nullable Boolean value) {
if (value == null) {
throw new SpelEvaluationException(SpelMessage.TYPE_CONVERSION_ERROR, "null", "boolean");
}
}
public DomailOpAnd(int pos, SpelNodeImpl ... operands) {
super(pos, operands);
}
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
//HERE is our non-short-circuiting logic:
boolean left = getBooleanValue(state, getLeftOperand());
boolean right = getBooleanValue(state, getRightOperand());
return BooleanTypedValue.forValue(left && right);
}
}
public class DomailOpOr extends OpOr {
public DomailOpOr(int pos, SpelNodeImpl ... operands) {
super(pos, operands);
}
@Override
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
Boolean left = DomailOpAnd.getBooleanValue(state, getLeftOperand());
Boolean right = DomailOpAnd.getBooleanValue(state, getRightOperand());
return BooleanTypedValue.forValue(left || right);
}
}
Now we have to use our versions of OpAnd and OpOr where they are created in InternalSpelExpressionParser. But InternalSpelExpressionParser is not a public class so we cannot use override as with OpAnd. We have to copy InternalSpelExpressionParser source code and create own class. Here I show only edited parts:
/**
* Hand-written SpEL parser. Instances are reusable but are not thread-safe.
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.0
*/
class DomailInternalExpressionParser extends TemplateAwareExpressionParser {
//rest of class ...
@Nullable
private SpelNodeImpl eatLogicalOrExpression() {
SpelNodeImpl expr = eatLogicalAndExpression();
while (peekIdentifierToken("or") || peekToken(TokenKind.SYMBOLIC_OR)) {
Token t = takeToken(); //consume OR
SpelNodeImpl rhExpr = eatLogicalAndExpression();
checkOperands(t, expr, rhExpr);
expr = new DomailOpOr(toPos(t), expr, rhExpr);
}
return expr;
}
@Nullable
private SpelNodeImpl eatLogicalAndExpression() {
SpelNodeImpl expr = eatRelationalExpression();
while (peekIdentifierToken("and") || peekToken(TokenKind.SYMBOLIC_AND)) {
Token t = takeToken(); // consume 'AND'
SpelNodeImpl rhExpr = eatRelationalExpression();
checkOperands(t, expr, rhExpr);
expr = new DomailOpAnd(toPos(t), expr, rhExpr);
}
return expr;
}
//rest of class ...
This also requires to copy source of other non public classes to be available:
Finally we swap implementation of parser in SpelExpression Parser:
import org.springframework.expression.ParseException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class DomailExpressionParser extends SpelExpressionParser {
private final SpelParserConfiguration configuration;
public DomailExpressionParser(SpelParserConfiguration configuration) {
super(configuration);
this.configuration = configuration;
}
// we cannot use this because configuration is not visible
// public DomailExpressionParser() {
// super();
// }
@Override
protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
return new DomailInternalExpressionParser(this.configuration).doParseExpression(expressionString, context);
}
}
and use our version of parser as if it would be SpelExpressionParser:
SpelParserConfiguration engineConfig = new SpelParserConfiguration();
ExpressionParser engine = new DomailExpressionParser(engineConfig);
Expression parsedScript = engine.parseExpression(script);
T result = parsedScript.getValue(scriptCtx, resultType);
We can test it on simple expression "false && blabla":
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.