[英]SpEL - disable short circuit evaluation
在我的應用程序中,我有具有多個SpEL
表達式的對象,這些表達式通常包含具有要調用的boolean
返回類型和邏輯運算符的方法簽名。 在緩存這些對象之前,我通過簡單地執行解析的表達式來檢查表達式的一致性。 當拋出異常時,我在對象內部設置了一個適當的標志來指示表達式無效以避免進一步執行。
我在EvaluationContext
上執行表達式,該表達式實現了允許成為表達式一部分的所有方法。 所有這些方法都返回false
。 我遇到了一個涉及短路評估的問題。
鑒於methodOne
和methodTwo
是唯一允許調用的方法,此表達式正確設置了不一致標志
methodERROROne("arg") AND methodTwo("arg")
然而,這不是因為methodOne
返回false
, Spring
使用短路評估並且不執行剩余的操作數。 這會導致表達式在真正的EvaluationContext
上執行時失敗,並且methodOne
返回true
methodOne("arg") AND methodERRORTwo("arg")
有沒有辦法禁用短路評估是 Spring 表達式語言?
不; OpAnd
運算符總是短路...
@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()));
}
...沒有等同於 Java 的&
運算符。
編輯
所有這些方法都返回 false
如果它們都返回false
你不能使用"!(!m1() and !m2())"
嗎?
甚至
"!((!m1() or m1()) and (!m2() or m2()))"
如果您真的想禁用短路評估,這是可能的,但您必須親自動手。 正如Gary 指出的 OpAnd (OpOr) 中的邏輯是固定的。 所以我們必須改變/覆蓋 OpAnd/OpOr.getValueInternal(ExpressionState state) 的實現。 這需要一些源代碼復制,所以源代碼可以在這里找到。 我的示例可能會有所不同,因為我實現了它並在不同版本的 maven 依賴項上進行了測試:
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
首先,我們必須實現我們自己版本的 OpAnd 和 OpOr。 由於 OpAnd.getBooleanValue(ExpressionState state, SpelNodeImpl operand) 和 SpelNodeImpl.getValue(ExpressionState state, Class desiredReturnType) 是私有的,我們必須實現我們的 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);
}
}
現在我們必須使用我們在 InternalSpelExpressionParser 中創建的 OpAnd 和 OpOr 版本。 但是 InternalSpelExpressionParser 不是公共類,所以我們不能像 OpAnd 那樣使用覆蓋。 我們必須復制 InternalSpelExpressionParser 源代碼並創建自己的類。 這里我只顯示編輯的部分:
/**
* 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 ...
這還需要復制其他非公共類的源代碼才能使用:
最后我們在 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);
}
}
並像使用 SpelExpressionParser 一樣使用我們的解析器版本:
SpelParserConfiguration engineConfig = new SpelParserConfiguration();
ExpressionParser engine = new DomailExpressionParser(engineConfig);
Expression parsedScript = engine.parseExpression(script);
T result = parsedScript.getValue(scriptCtx, resultType);
我們可以用簡單的表達式“false && blabla”來測試它:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.