0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

How thymeleaf processes value of "th:text" attribute

Posted at

Assumption

In this passage Thymeleaf template engine which used in Spring MVC is discussed. Code shown is extracted from Thymeleaf 3.0.11-RELEASE and Spring MVC 5.1.9-RELEASE. Please notes that behavior of Thymeleaf used in other environment may be different.

How?

First look into following code.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Example 1</title>
  <meta charset="utf-8">
</head>
<body>
<div>
  <div th:text="a == b"></div>
</div>
</body>
</html>

After process, result is

<!DOCTYPE html>
<html>
<head>
  <title>Example 1</title>
  <meta charset="utf-8">
</head>
<body>
<div>
  <div>false</div>
</div>
</body>
</html>

How thymeleaf processes attribute "th:text" of tag "<div th:text="a == b"></div>"? Attribute is handle by class org.thymeleaf.standard.processor.StandardTextTagProcessor.

public final class StandardTextTagProcessor extends AbstractStandardExpressionAttributeTagProcessor {
    // ...
}

Look into its parent class org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor, in function doProcess(ITemplateContext, IProcessableElementTag, AttributeName, String, IElementTagStructureHandler), parameter "attributeValue" is "a == b", which is value of attribute "th:text".

public abstract class AbstractStandardExpressionAttributeTagProcessor extends AbstractAttributeTagProcessor {

    @Override
    protected final void doProcess(
            final ITemplateContext context,
            final IProcessableElementTag tag,
            final AttributeName attributeName,
            final String attributeValue,
            final IElementTagStructureHandler structureHandler) {

        final Object expressionResult;
        if (attributeValue != null) {

            final IStandardExpression expression = EngineEventUtils.computeAttributeExpression(context, tag, attributeName, attributeValue);

            if (expression != null && expression instanceof FragmentExpression) {

                // ...

            } else {

                /*
                 * Some attributes will require the execution of the expressions contained in them in RESTRICTED
                 * mode, so that e.g. access to request parameters is forbidden.
                 */
                expressionResult = expression.execute(context, this.expressionExecutionContext);

            }

        } else {
            expressionResult = null;
        }

        // If the result of this expression is NO-OP, there is nothing to execute
        if (expressionResult == NoOpToken.VALUE) {
            if (this.removeIfNoop) {
                structureHandler.removeAttribute(attributeName);
            }
            return;
        }

        doProcess(
                context, tag,
                attributeName, attributeValue,
                expressionResult, structureHandler);

    }
    // ...
}

In function org.thymeleaf.engine.EngineEventUtils.computeAttributeExpression(ITemplateContext, IProcessableElementTag, AttributeName, String), "a == b" will be changed to express object org.thymeleaf.standard.expression.EqualsExpression, with interface org.thymeleaf.standard.expression.IStandardExpression.

EqualsExpression.png

Following is class implements org.thymeleaf.standard.expression.IStandardExpression

IStandardExpression.png

And then value of expression is obtained from function

expressionResult = expression.execute(context, this.expressionExecutionContext);

java.lang.Boolean object which represent "false" value is assigned to variable expressionResult. And then text representation is obtain from function

doProcess(context, tag, attributeName, attributeValue, expressionResult, structureHandler);

Processing is finished.

Expressions in Thymeleaf

From Thymeleaf document, there is 5 type of expressions.

Name Symbol Expression class
Variable Expressions ${...} org.thymeleaf.standard.expression.VariableExpression
Selection Variable Expressions *{...} org.thymeleaf.standard.expression.SelectionVariableExpression
Message Expressions #{...} org.thymeleaf.standard.expression.MessageExpression
Link URL Expressions @{...} org.thymeleaf.standard.expression.LinkExpression
Fragment Expressions ~{...} org.thymeleaf.standard.expression.FragmentExpression

I will discuss message expressions and variable expressions. For remaining may lookup source code of class states above.

Message expressions

First look into following code.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title th:text="#{applicationName}"></title>
  <meta charset="utf-8">
</head>
<body></body>
</html>

Values defined in resources\messages.properties.

applicationName=Example 2

After process, result is

<!DOCTYPE html>
<html>
<head>
  <title>Example 2</title>
  <meta charset="utf-8">
</head>
<body></body>
</html>

Property value "#{applicationName}" is represent by expression object org.thymeleaf.standard.expression.MessageExpression. And value is evaluated by static function executeMessageExpression(IExpressionContext, MessageExpression, StandardExpressionExecutionContext).

public final class MessageExpression extends SimpleExpression {

    static Object executeMessageExpression(
            final IExpressionContext context,
            final MessageExpression expression, final StandardExpressionExecutionContext expContext) {

        // ...

        final ITemplateContext templateContext = (ITemplateContext)context;

        final IStandardExpression baseExpression = expression.getBase();
        Object messageKey = baseExpression.execute(templateContext, expContext);
        messageKey = LiteralValue.unwrap(messageKey);
        if (messageKey != null && !(messageKey instanceof String)) {
            messageKey = messageKey.toString();
        }
        if (StringUtils.isEmptyOrWhitespace((String) messageKey)) {
            throw new TemplateProcessingException(
                    "Message key for message resolution must be a non-null and non-empty String");
        }
        

        final Object[] messageParameters;
        if (expression.hasParameters()) {

            // ...

        } else {
            messageParameters = NO_PARAMETERS;
        }

        return templateContext.getMessage(null, (String)messageKey, messageParameters, true);
        
    }
    // ...
}

In some case key for lookup message is from other expression such as variable expression, so code below is for evaluate the expression inside #{...} bracket.

Object messageKey = baseExpression.execute(templateContext, expContext);

In this example, "applicationName" is return which is the same as input. Now using this key to lookup the value by following function.

return templateContext.getMessage(null, (String)messageKey, messageParameters, true);

templateContext is instance of class org.thymeleaf.context.WebEngineContext. Function getMessage(Class>, String, Object[], boolean) is defined in its parent class org.thymeleaf.context.AbstractEngineContext.

public abstract class AbstractEngineContext implements IEngineContext {

    public final String getMessage(
            final Class<?> origin, final String key, final Object[] messageParameters, final boolean useAbsentMessageRepresentation) {

        // ...

        // only have one messageResolvers: org.thymeleaf.spring5.messageresolver.SpringMessageResolver
        final Set<IMessageResolver> messageResolvers = this.configuration.getMessageResolvers();

        // Try to resolve the message
        for (final IMessageResolver messageResolver : messageResolvers) {
            final String resolvedMessage =
                    messageResolver.resolveMessage(this, origin, key, messageParameters);
            if (resolvedMessage != null) {
                return resolvedMessage;
            }
        }

        if (useAbsentMessageRepresentation) {
            // ...
        }

        return null;
    }
    // ...
}

In class org.thymeleaf.spring5.messageresolver.SpringMessageResolver, resolveMessage(ITemplateContext, Class>, String, Object[]) actually call org.springframework.context.support.AbstractApplicationContext.getMessage(String, Object[], Locale), facility provided by Spring's application context.

Variable expressions

First look into following code.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Example 3</title>
  <meta charset="utf-8">
</head>
<body>
  <div th:text="${message}"></div>
  <div th:text="${#locale.language}"></div>
</body>
</html>

And model attributes are added in controller

public String index(Model model) {
  model.addAttribute("message", "Model attribute");
  return "index";
}

After process, result is

<!DOCTYPE html>
<html>
<head>
  <title>Example 3</title>
  <meta charset="utf-8">
</head>
<body>
  <div>Model attribute</div>
  <div>en</div>
</body>
</html>

Property value "${message}" and "${#locale.language}" are represent by expression object org.thymeleaf.standard.expression.VariableExpression. And value of expression is evaluated by static function executeVariableExpression(IExpressionContext, VariableExpression, IStandardVariableExpressionEvaluator, StandardExpressionExecutionContext).

public final class VariableExpression extends SimpleExpression implements IStandardVariableExpression {

    static Object executeVariableExpression(
            final IExpressionContext context,
            final VariableExpression expression, final IStandardVariableExpressionEvaluator expressionEvaluator,
            final StandardExpressionExecutionContext expContext) {

        if (logger.isTraceEnabled()) {
            logger.trace("[THYMELEAF][{}] Evaluating variable expression: \"{}\"", TemplateEngine.threadIndex(), expression.getStringRepresentation());
        }
        
        final StandardExpressionExecutionContext evalExpContext =
            (expression.getConvertToString()? expContext.withTypeConversion() : expContext.withoutTypeConversion());

        final Object result = expressionEvaluator.evaluate(context, expression, evalExpContext);

        if (!expContext.getForbidUnsafeExpressionResults()) {
            return result;
        }

        // ...
    }
    // ...
}

Actual value of expression is evaluated by

final Object result = expressionEvaluator.evaluate(context, expression, evalExpContext);

Variable "expressionEvaluator" is instance of class org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator. Variable "context" is instance of class org.thymeleaf.context.WebEngineContext. Variable "expression" is instance of class org.thymeleaf.standard.expression.VariableExpression which represent "message" and "#locale.language". Variable "evalExpContext" is instance of org.thymeleaf.standard.expression.StandardExpressionExecutionContext. On function org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(IExpressionContext, IStandardVariableExpression, StandardExpressionExecutionContext)

public class SPELVariableExpressionEvaluator
        implements IStandardVariableExpressionEvaluator {

    public final Object evaluate(
            final IExpressionContext context,
            final IStandardVariableExpression expression,
            final StandardExpressionExecutionContext expContext) {
        
        if (logger.isTraceEnabled()) {
            logger.trace("[THYMELEAF][{}] SpringEL expression: evaluating expression \"{}\" on target", TemplateEngine.threadIndex(), expression.getExpression());
        }

        try {

            final String spelExpression = expression.getExpression();
            final boolean useSelectionAsRoot = expression.getUseSelectionAsRoot();

            if (spelExpression == null) {
                throw new TemplateProcessingException("Expression content is null, which is not allowed");
            }

            /*
             * TRY TO DELEGATE EVALUATION TO SPRING IF EXPRESSION IS ON A BOUND OBJECT
             */
            if (expContext.getPerformTypeConversion()) {
                // ...
            }

            final IEngineConfiguration configuration = context.getConfiguration();


            /*
             * OBTAIN THE EXPRESSION (SpelExpression OBJECT) FROM THE CACHE, OR PARSE IT
             */
            final ComputedSpelExpression exp = obtainComputedSpelExpression(configuration, expression, spelExpression);


            /*
             * COMPUTE EXPRESSION OBJECTS AND ADDITIONAL CONTEXT VARIABLES MAP
             * The IExpressionObjects implementation returned by processing contexts that include the Standard
             * Dialects will be lazy in the creation of expression objects (i.e. they won't be created until really
             * needed).
             */
            final IExpressionObjects expressionObjects =
                    (exp.mightNeedExpressionObjects? context.getExpressionObjects() : null);


            /*
             * CREATE/OBTAIN THE SPEL EVALUATION CONTEXT OBJECT
             */
            EvaluationContext evaluationContext =
                    (EvaluationContext) context.
                            getVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);

            if (evaluationContext == null) {
                // ...
            } else if (!(evaluationContext instanceof IThymeleafEvaluationContext)) {
                // ...
            }


            /*
             * 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()) {
                return exp.expression.getValue(thymeleafEvaluationContext, evaluationRoot);
            }

            // ...

        } catch (final TemplateProcessingException e) {
            throw e;
        } catch(final Exception e) {
            throw new TemplateProcessingException(
                    "Exception evaluating SpringEL expression: \"" + expression.getExpression() + "\"", e);
        }
        
    }
    // ...
}

"message", "#locale.language" read from VariableExpression and SpelExpression instance is built (or get from cache if available) on following

final ComputedSpelExpression exp = obtainComputedSpelExpression(configuration, expression, spelExpression);

EvaluationContext which used by SpelExpression is first saved into model variable with key "thymeleaf::EvaluationContext" (defined in org.thymeleaf.spring5.expression.ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME) in function org.thymeleaf.spring5.view.ThymeleafView.renderFragment(Set, Map, HttpServletRequest, HttpServletResponse). And extracted on following

EvaluationContext evaluationContext =
                    (EvaluationContext) context.
                            getVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);

SpelExpression is finally evaluated on following

return exp.expression.getValue(thymeleafEvaluationContext, evaluationRoot);

SpEL variable can be obtained by function
org.thymeleaf.spring5.expression.ThymeleafEvaluationContext.lookupVariable(String). Model variable can be obtained from evaluationRoot variable, which is instance of org.thymeleaf.spring5.expression.SPELContextMapWrapper. SPELContextMapWrapper is a wrapper of org.thymeleaf.context.WebEngineContext. Model variable can be obtained from WebEngineContext by getVariable(String) function.

SpEL variable which can be used in SpEL when writing variable expression

In SpEL, variable can be referenced in the expression using the syntax #variableName. following pre-defined variable can be used on writing expression in Thymeleaf template.

org.thymeleaf.spring5.expression.SpringStandardExpressionObjectFactory

Name Class of instance
#fields org.thymeleaf.spring5.expression.Mvc
#themes org.thymeleaf.spring5.expression.Themes
#mvc org.thymeleaf.spring5.expression.Fields
#requestdatavalues org.thymeleaf.spring5.expression.RequestDataValues

org.thymeleaf.standard.expression.StandardExpressionObjectFactory

Name Class of instance
#ctx class implements org.thymeleaf.context.IExpressionContext
#root class implements org.thymeleaf.context.IExpressionContext
#vars class implements org.thymeleaf.context.IExpressionContext
#object java.lang.Object or class implements org.thymeleaf.context.IExpressionContext
#locale java.util.Locale
#request class implements javax.servlet.http.HttpServletRequest
#response class implements javax.servlet.http.HttpServletResponse
#session class implements javax.servlet.http.HttpSession
#servletContext class implements javax.servlet.ServletContext
#conversions org.thymeleaf.expression.Conversions
#uris org.thymeleaf.expression.Uris
#calendars org.thymeleaf.expression.Calendars
#dates org.thymeleaf.expression.Dates
#bools org.thymeleaf.expression.Bools
#numbers org.thymeleaf.expression.Numbers
#objects org.thymeleaf.expression.Objects
#strings org.thymeleaf.expression.Strings
#arrays org.thymeleaf.expression.Arrays
#lists org.thymeleaf.expression.Lists
#sets org.thymeleaf.expression.Sets
#maps org.thymeleaf.expression.Maps
#aggregates org.thymeleaf.expression.Aggregates
#messages org.thymeleaf.expression.Messages
#ids org.thymeleaf.expression.Ids
#execInfo org.thymeleaf.expression.ExecutionInfo
#httpServletRequest (Deprecated) class implements javax.servlet.http.HttpServletRequest
#httpSession (Deprecated) class implements javax.servlet.http.HttpSession

org.thymeleaf.extras.java8time.dialect.Java8TimeExpressionFactory

Name Class of instance
#temporals org.thymeleaf.extras.java8time.expression.Temporals

org.thymeleaf.extras.springsecurity5.dialect.expression.SpringSecurityExpressionObjectFactory

Name Class of instance
#authentication class implements org.springframework.security.core.Authentication
#authorization org.thymeleaf.extras.springsecurity5.auth.Authorization

Model variable which can be used in SpEL when writing variable expression

Name Class of instance
session org.thymeleaf.context.WebEngineContext$SessionAttributesMap
param org.thymeleaf.context.WebEngineContext$RequestParametersMap
application org.thymeleaf.context.WebEngineContext$ServletContextAttributesMap
thymeleaf::EvaluationContext org.thymeleaf.spring5.expression.ThymeleafEvaluationContext
thymeleafRequestContext org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext
springMacroRequestContext org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext
springRequestContext org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?