SpEL - 禁用短路评估



在我的应用程序中,我有几个SpEL表达式的对象,这些表达式通常包含具有要调用boolean返回类型和逻辑运算符的方法的签名。在缓存这些对象之前,我通过简单地执行解析的表达式来检查表达式的一致性。当抛出异常时,我在对象内设置了一个适当的标志,以指示表达式无效以避免进一步执行。

我正在实现允许成为表达式一部分的所有方法的EvaluationContext上执行表达式。所有这些方法都返回false.我遇到了一个涉及短路评估的问题。

鉴于methodOnemethodTwo是唯一允许调用的方法,此表达式正确设置了不一致标志

methodERROROne("arg") AND methodTwo("arg")

然而,这个不是,因为methodOne返回falseSpring使用短路计算并且不执行剩余的操作数。这会导致表达式在实际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);
}
}

现在我们必须使用我们的OpAnd和OpOr版本,它们是在InternalSpelExpressionParser中创建的。但是 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 ... 

这还需要复制其他非公共类的源代码才能使用:

  • org.springframework.expression.spel.standard.Token
  • org.springframework.expression.spel.standard.Tokenizer
  • org.springframework.expression.spel.standard.TokenKind

最后,我们在 SpelExpression 解析器中交换解析器的实现:

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"上测试它:

  • 使用SpelExpressionParser没有错误
  • 使用 DomailExpressionParser 我得到 org.springframework.expression.spel.SpelEvaluationException: EL1008E:在类型为"sk.dominanz.domic.common.eml.service.EmlScriptRootObject"的对象上找不到属性或字段 'blabla' - 也许不是公共的或无效的?

最新更新