Неожиданный результат с использованием арифметики Thymeleaf

#java #html #thymeleaf #ognl

#java #HTML #thymeleaf #ognl

Вопрос:

У меня есть шаблон 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

Но если одно (или оба) значения модели равно null:

 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: невозможно выполнить вычитание: операнды «null» и «7»

Почему тест 2 генерирует действительный HTML-код с неожиданным результатом вычисления вместо того, чтобы выдавать ошибку?

(Я понимаю, что мне нужно заранее обработать эти null значения, чтобы избежать проблем, но это молчаливое отсутствие сбоев является проблемой.)

Ответ №1:

Быстрый ответ

Вы должны обрабатывать null значения явно заранее, как указано в вопросе.

Вы также должны знать о несогласованном поведении между оператором минус Thymeleaf и оператором минус OGNL.

Рекомендация: используйте оператор Thymeleaf минус.

Далее следует более подробное объяснение…

Thymeleaf и OGNL

При использовании стандартного диалекта Thymeleaf выражения внутри открытия ${ и закрытия } обрабатываются OGNL. Из официальной документации:

Это выражение переменной, и оно содержит выражение на языке, называемом OGNL (Object-Graph Navigation Language), которое будет выполнено на карте контекстных переменных…

Вы можете прочитать об OGNL и его синтаксисе здесь . Thymeleaf версии 3.0.12 использует ognl-3.1.26.jar библиотеку.

Итак, когда вы используете ${total - active} , все это выражение, включая вычитание, обрабатывается OGNL.

Однако, когда вы используете ${total} - ${active} , это фактически два отдельных выражения OGNL со знаком минус между ними.

В первом случае ответственность за выполнение вычитания лежит на OGNL. Но во втором случае вычитание выполняется Thymeleaf после того, как он делегировал оценку двух ${...} выражений в OGNL.

Thymeleaf обычно показывает то же поведение, что и OGNL для большинства своих операторов. Но в этом конкретном случае оператор Thymeleaf minus при обработке значений ведет себя иначе, чем оператор OGNL minus null .

Я бы сказал, что поведение Thymeleaf является менее неожиданным подходом.

Spring и SpEL

Если вы используете Spring с Thymeleaf, то все выражения внутри ${ и } делегируются SpEL (языку выражений Spring), а OGNL вообще не используется.

В этом случае выражение ${total - active} больше не будет «успешным» во время выполнения. Вместо этого он выдаст исключение SpEL:

org.springframework.expression.spel.Исключение SpelEvaluationException: EL1030E: оператор ‘SUBTRACT’ не поддерживается между объектами типа ‘null’ и ‘java.lang.Целое число ‘

Итак, SpEL и стандартный диалект Thymeleaf совместимы друг с другом, а OGNL находится в меньшинстве.

Действительно ли OGNL оценивает null как ноль здесь?

Да — и мы можем продемонстрировать это, используя OGNL в простой программе Java:

Зависимость:

 <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 за то, что они указали мне правильное направление для решения этой проблемы:

Неожиданный результат вычитания с использованием нулей


Postscript

Выражение OGNL:

 "1 / null"
 

вычисляется в Java Double.POSITIVE_INFINITY по тем же причинам, что и описанное выше поведение — where null преобразуется в 0.0 .