How thymeleaf processes value of "th:text" attribute (2)
Tags: spring, thymeleaf, springmvc
Published: 2024-01-31 19:28:17

## Notice

I wrote this article and was originally published on [Qiita](https://qiita.com/saladlam/items/f223b8ef98abb21b96fa) on 3 September 2022.

---

# Introduction

Last [article](https://dev.to/saladlam/how-thymeleaf-processes-value-of-thtext-attribute-3faa) 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?

```html
<!-- (a) --><div th:text="a + 1"></div>
<!-- (b) --><div th:text="${a + 1}"></div>
<!-- (c) --><div th:text="${a} + ${1}"></div>
```

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.

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

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](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#messages)).
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.

```java
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;

    //...
}
```

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()*.

```java
    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);
            }
    }
```

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)
1. *org.springframework.core.convert.ConversionService* (e.g. convert TimeZone from String)
1. *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.