使用 Thymeleaf 算术的意外结果

Unexpected result using Thymeleaf arithmetic

我有一个 Thymeleaf 模板,没有使用任何 Spring 或 SpEL - 只是 Thymeleaf 标准方言。

模板的相关部分是:

<div>
    <span>Inactive: </span>
    <span th:text="${total - active}"></span>
</div>

测试 1

如果我的模型填充如下:

model.put("total", 10);
model.put("active", 7);

然后我得到了预期的结果:

<div>
    <span>Inactive: </span>
    <span>3</span>
</div>

测试 2

但是如果模型值中的一个(或两个)为空:

model.put("total", null);
model.put("active", 7);

然后我得到了意想不到的结果。有了上面的数据,我得到:

<div>
    <span>Inactive: </span>
    <span>-7.0</span>
</div>

换句话说,null被计算为浮点数0.0,因此计算结果为-7.0

我原以为它会抛出一个运行时错误,这是由于尝试对空值执行算术而引起的。

测试 3

如果我稍微更改模板并使用与测试 2 相同的数据:

<div>
    <span>Inactive: </span>
    <span th:text="${total} - ${active}"></span>
</div>

我收到以下运行时错误 - 这是我对测试 2 的预期:

org.thymeleaf.exceptions.TemplateProcessingException: Cannot execute subtraction: operands are "null" and "7"

为什么测试 2 生成了有效的 HTML 而不是抛出错误的计算结果?

(我知道我需要预先处理这些 null 值以避免出现问题,但这种无声的非故障是一个问题。)

快速解答

您应该明确地预先处理 null 值,如问题中所述。

您还应注意 Thymeleaf 的减号运算符与 OGNL 的减号运算符之间的不一致行为。

建议:使用 Thymeleaf 减号运算符。

更详细的解释如下...

Thymeleaf 和 OGNL

当您使用标准 Thymeleaf 方言时,开头 ${ 和结尾 } 内的表达式由 OGNL 处理。来自 the official documentation:

This is a variable expression, and it contains an expression in a language called OGNL (Object-Graph Navigation Language) that will be executed on the context variables map...

您可以阅读有关 OGNL 及其语法的信息 here。 Thymeleaf 版本 3.0.12 使用 ognl-3.1.26.jar 库。

因此,当您使用 ${total - active} 时,整个表达式(包括减法)都由 OGNL 处理。

但是,当您使用 ${total} - ${active} 时,这实际上是两个单独的 OGNL 表达式,它们之间有一个减号。

在第一种情况下,执行减法是 OGNL 的责任。但在第二种情况下,减法是由 Thymeleaf 在将两个 ${...} 表达式的评估委托给 OGNL 之后执行的。

Thymeleaf 的大多数运算符通常表现出与 OGNL 相同的行为。但在这种特定情况下,Thymeleaf 减号运算符在处理 null 值时与 OGNL 减号运算符 的行为不同。

我认为 Thymeleaf 行为是不那么令人惊讶的方法。

Spring 和 SpEL

如果您将 Spring 与 Thymeleaf 一起使用,则 ${} 中的所有表达式都将委托给 SpEL(Spring Expression Language)- 并且 OGNL 不会在全部.

在这种情况下,表达式 ${total - active} 将不再在运行时“成功”。相反,它会抛出 SpEL 异常:

org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'SUBTRACT' is not supported between objects of type 'null' and 'java.lang.Integer'

因此,SpEL 和 Thymeleaf 标准方言彼此一致 - 而 OGNL 是少数。

OGNL 真的在这里将 null 评估为零吗?

是的 - 我们可以证明在一个简单的 Java 程序中使用 OGNL:

依赖关系:

<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>3.3.0</version>
    <!-- same behavior:
    <version>3.2.21</version>
    -->
</dependency>

Java演示:

import ognl.Ognl;
import ognl.OgnlException;

public class NullDemo {
    
    public void run() throws OgnlException {        
        // this uses a null context (2nd parameter), because we have 
        // a simple hard-coded subtraction expression:
        Object result = Ognl.getValue("null - 7", null);
        System.out.println(result);
        
    }
    
}

输出为:-7.0,与问题中的测试 #2 相同。


致谢

我非常感谢 Thymeleaf 团队为我指明了解决这个问题的正确方向:

Unexpected result from subtraction using nulls


后记

OGNL 表达式:

"1 / null"

计算为 Java 的 Double.POSITIVE_INFINITY,原因与上述行为相同 - 其中 null 转换为 0.0