How thymeleaf processes value of "th:text" attribute (2)

saladlam

Salad Lam

Posted on January 31, 2024

How thymeleaf processes value of "th:text" attribute (2)

Notice

I wrote this article and was originally published on Qiita on 3 September 2022.


Introduction

Last article is published around 3 years ago, but some of the issue is not discussed. Now I will continue to discuss.

Meaning of ${}

Please read following example. Although all of them will have the same result, do you know the different between them?

<!-- (a) --><div th:text="a + 1"></div>
<!-- (b) --><div th:text="${a + 1}"></div>
<!-- (c) --><div th:text="${a} + ${1}"></div>
Enter fullscreen mode Exit fullscreen mode

Simple to say, expression inside ${} will be pass to SpEL for evaluate the value.
In (a), expression "a + 1" will be evaluate by Thymeleaf.
In (b), expression "a + 1" first is pass to SpEL to evaluate, and the result "a1" then pass to Thymeleaf to evaluate.
In (c), SpEL will evaluate "a" and "1", and then Thymeleaf join the result and then continue to evaluate expression "a + 1".

Beware of the position of symbol

Please read following example. The position of hash is different, and the meaning is totally different.

<!-- (a) --><div th:text="#{home.welcome}"></div>
<!-- (b) --><div th:text="${#bools.isTrue(obj)}? 'YES': 'NO'"></div>
Enter fullscreen mode Exit fullscreen mode

In (a), #{} is message expression. The expression in curly bracket is evaluate by Thymeleaf only, so bean lookup symbol "@" of SpEL will not work. In addition the express is treat as TextLiteralExpression, all expression utility objects (e.g. #dates.format()) provided by Thymeleaf will not work also (except passing message parameter).
In (b), prefix # means to refer expression objects such as expression utility objects.

Different between ${} and *{}

To explain this, first I need to introduce a interface of SpEL shown below.

public interface org.springframework.expression.Expression {
    /**
     * Evaluate this expression in the provided context and return the result
     * of evaluation, but use the supplied root context as an override for any
     * default root object specified in the context.
     * @param context the context in which to evaluate the expression
     * @param rootObject the root object against which to evaluate the expression
     * @return the evaluation result
     * @throws EvaluationException if there is a problem during evaluation
     */
    @Nullable
    Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException;

    //...
}
Enter fullscreen mode Exit fullscreen mode

Simple to say, to access object in a SpEL expression, first to lookup in rootObject. If not found then lookup in context. You can find this method used in org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator#evaluate().

    public final Object evaluate(
            final IExpressionContext context,
            final IStandardVariableExpression expression,
            final StandardExpressionExecutionContext expContext) {
    //...
            /*
             * AT THIS POINT, WE ARE SURE IT IS AN IThymeleafEvaluationContext
             *
             * This is needed in order to be sure we can modify the 'requestParametersRestricted' flag and also the
             * expression objects.
             */
            final IThymeleafEvaluationContext thymeleafEvaluationContext = (IThymeleafEvaluationContext) evaluationContext;


            /*
             * CONFIGURE THE IThymeleafEvaluationContext INSTANCE: expression objects and restrictions
             *
             * NOTE this is possible even if the evaluation context object is shared for the whole template execution
             * because evaluation contexts are not thread-safe and are only used in a single template execution
             */
            thymeleafEvaluationContext.setExpressionObjects(expressionObjects);
            thymeleafEvaluationContext.setVariableAccessRestricted(expContext.getRestrictVariableAccess());


            /*
             * RESOLVE THE EVALUATION ROOT
             */
            final ITemplateContext templateContext = (context instanceof ITemplateContext ? (ITemplateContext) context : null);
            final Object evaluationRoot =
                    (useSelectionAsRoot && templateContext != null && templateContext.hasSelectionTarget()?
                            templateContext.getSelectionTarget() : new SPELContextMapWrapper(context, thymeleafEvaluationContext));


            /*
             * If no conversion is to be made, JUST RETURN
             */
            if (!expContext.getPerformTypeConversion()) {
                // HERE
                return exp.expression.getValue(thymeleafEvaluationContext, evaluationRoot);
            }
    }
Enter fullscreen mode Exit fullscreen mode

Value of parameter thymeleafEvaluationContext is org.thymeleaf.spring5.expression.ThymeleafEvaluationContext. In class ThymeleafEvaluationContext, it contains three object references.

  1. org.springframework.context.ApplicationContext (e.g. lookup beans)
  2. org.springframework.core.convert.ConversionService (e.g. convert TimeZone from String)
  3. org.thymeleaf.expression.ExpressionObjects (e.g. using #bools.isTrue(obj))

The different between ${} and **{}* is value of parameter of evaluationRoot. For ${}, evaluationRoot is org.thymeleaf.spring5.expression.SPELContextMapWrapper. And {}, evaluationRoot is object selected by **th:object* attribute of parent tab.

💖 💪 🙅 🚩
saladlam
Salad Lam

Posted on January 31, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related