标准表达式语法


我们将在杂货虚拟商店的开发中稍作休息,以了解Thymeleaf标准方言中最重要的部分之一:Thymeleaf标准表达式语法。

我们已经看到在这种语法中表达的两种类型的有效属性值:消息和变量表达式:

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>

<p>Today is: <span th:text="${today}">13 february 2011</span></p>

但是有更多类型的表达式,以及更多有趣的细节来了解我们已经知道的那些。首先,让我们看一下标准表达式功能的快速摘要:

  • Simple expressions:

    • Variable Expressions: ${...}
    • Selection Variable Expressions: * {...}
    • Message Expressions: #{...}
    • Link URL Expressions: @{...}
    • Fragment Expressions: ~{...}
  • Literals

    • Text literals: 'one text', 'Another one!',…
    • Number literals: 0, 34, 3.0, 12.3,…
    • Boolean literals: true, false
    • Null literal: null
    • Literal tokens: one, sometext, main,…
  • Text operations:

    • String concatenation: +
    • Literal substitutions: |The name is ${name}|
  • Arithmetic operations:

    • Binary operators: +, -, * , /, %
    • Minus sign (unary operator): -
  • Boolean operations:

    • Binary operators: and, or
    • Boolean negation (unary operator): !, not
  • Comparisons and equality:

    • Comparators: >, <, >=, <= (gt, lt, ge, le)
    • Equality operators: ==, != (eq, ne)
  • Conditional operators:

    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)
  • Special tokens:

    • No-Operation: _

所有这些功能都可以组合和嵌套:

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

4.1Messages

我们已经知道,#{...}消息表达式允许我们链接:

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>

.....对此:

home.welcome=¡Bienvenido a nuestra tienda de comestibles!

但是有一个方面我们还没有想到:如果消息文本不是完全静态会发生什么?例如,如果我们的应用程序知道谁是随时访问该网站的用户并且我们想要通过名字问候他们怎么办?

<p>¡Bienvenido a nuestra tienda de comestibles, John Apricot!</p>

这意味着我们需要在消息中添加一个参数。像这样:

home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!

参数是根据java.text.MessageFormat标准语法指定的,这意味着您可以格式化为java.text. * 包中的类的API文档中指定的数字和日期。

为了为我们的参数指定一个值,并给定一个调用的HTTP会话属性user,我们可以:

请注意,使用th:utext此处意味着格式化的消息不会被转义。此示例假定user.name已经转义。

可以指定几个参数,以逗号分隔。

消息密钥本身可以来自变量:

<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
  Welcome to our grocery store, Sebastian Pepper!
</p>

4.2Variables

我们已经提到${...}表达式实际上是在上下文中包含的变量映射上执行的OGNL(对象 - 图形导航语言)表达式。

有关OGNL语法和功能的详细信息,请阅读OGNL语言指南 在Spring支持MVC的应用程序中,OGNL将被SpringEL取代,但其语法与OGNL的语法非常相似(实际上,对于大多数常见情况来说,完全相同)。

从OGNL的语法,我们知道表达式:

<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>

......实际上相当于这个:

ctx.getVariable("today");

但是OGNL允许我们创建更强大的表达式,这就是:

<p th:utext="#{home.welcome(${session.user.name})}">
  Welcome to our grocery store, Sebastian Pepper!
</p>

...通过执行以下命令获取用户名:

((User) ctx.getVariable("session").get("user")).getName();

但是getter方法导航只是OGNL的一个特性。让我们看看更多:

/*
 * Access to properties using the point (.). Equivalent to calling property getters.
 */
${person.father.name}

/*
 * Access to properties can also be made by using brackets ([]) and writing
 * the name of the property as a variable or between single quotes.
 */
${person['father']['name']}

/*
 * If the object is a map, both dot and bracket syntax will be equivalent to
 * executing a call on its get(...) method.
 */
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}

/*
 * Indexed access to arrays or collections is also performed with brackets,
 * writing the index without quotes.
 */
${personsArray[0].name}

/*
 * Methods can be called, even with arguments.
 */
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}

Expression Basic Objects

在上下文变量中评估OGNL表达式时,某些对象可用于表达式以获得更高的灵活性。将以#符号开头引用这些对象(按照OGNL标准):

  • #ctx: the context object.
  • #vars: the context variables.
  • #locale: the context locale.
  • #request: (only in Web Contexts) the HttpServletRequest object.
  • #response: (only in Web Contexts) the HttpServletResponse object.
  • #session: (only in Web Contexts) the HttpSession object.
  • #servletContext: (only in Web Contexts) the ServletContext object.

所以我们可以这样做:

Established locale country: <span th:text="${#locale.country}">US</span>.

您可以在附录A中阅读这些对象的完整参考。

Expression Utility Objects

除了这些基本对象,Thymeleaf还将为我们提供一组实用程序对象,帮助我们在表达式中执行常见任务。

  • #execInfo: information about the template being processed.
  • #messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
  • #uris: methods for escaping parts of URLs/URIs
  • #conversions: methods for executing the configured conversion service (if any).
  • #dates: methods for java.util.Date objects: formatting, component extraction, etc.
  • #calendars: analogous to #dates, but for java.util.Calendar objects.
  • #numbers: methods for formatting numeric objects.
  • #strings: methods for String objects: contains, startsWith, prepending/appending, etc.
  • #objects: methods for objects in general.
  • #bools: methods for boolean evaluation.
  • #arrays: methods for arrays.
  • #lists: methods for lists.
  • #sets: methods for sets.
  • #maps: methods for maps.
  • #aggregates: methods for creating aggregates on arrays or collections.
  • #ids: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

您可以在附录B中查看每个实用程序对象提供的功能。

Reformatting dates in our home page

现在我们知道这些实用程序对象,我们可以使用它们来改变我们在主页中显示日期的方式。而不是在我们这样做HomeController:

SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar cal = Calendar.getInstance();

WebContext ctx = new WebContext(request, servletContext, request.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));

templateEngine.process("home", ctx, response.getWriter());

......我们可以做到这一点:

WebContext ctx =
    new WebContext(request, response, servletContext, request.getLocale());
ctx.setVariable("today", Calendar.getInstance());

templateEngine.process("home", ctx, response.getWriter());

...然后在视图层本身中执行日期格式设置:

<p>
  Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>

4.3 Expressions on selections (asterisk syntax)

变量表达式不仅可以写成${...},也可以作为*{...}。

但是有一个重要的区别:星号语法评估所选对象上的表达式而不是整个上下文。也就是说,只要没有选定的对象,美元和星号语法就会完全相同。

什么是选定的对象?使用th:object属性的表达式的结果。我们在用户个人资料(userprofile.html)页面中使用一个:

<div th:object="${session.user}">
   <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
   <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
   <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
 </div>

这完全等同于:

<div>
  <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

当然,美元和星号语法可以混合使用:

<div th:object="${session.user}">
  <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

当对象选择到位时,所选对象也可用作美元表达式作为#object表达式变量:

<div th:object="${session.user}">
  <p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

如上所述,如果没有执行任何对象选择,则美元和星号语法是等效的。

<div>
  <p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
  <p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p>
</div>

4.4Link URLs

由于它们的重要性,URL是Web应用程序模板中的一等公民,而Thymeleaf标准方言具有特殊的语法,@语法:@{...}

有不同类型的网址:

  • Absolute URLs: http://www.thymeleaf.org
  • Relative URLs, which can be:
    • Page-relative: user/login.html
    • Context-relative: /itemdetails?id=3 (context name in server will be added automatically)
    • Server-relative: ~/billing/processInvoice (allows calling URLs in another context (= application) in the same server.
    • Protocol-relative URLs: //code.jquery.com/jquery-2.0.3.min.js

这些表达式的实际处理及其转换为将要输出的URL是通过org.thymeleaf.linkbuilder.ILinkBuilder注册到ITemplateEngine正在使用的对象中的接口的实现来完成的。

默认情况下,此接口的单个​​实现是在类中注册的org.thymeleaf.linkbuilder.StandardLinkBuilder,这对于脱机(非Web)以及基于Servlet API的Web方案都是足够的。其他方案(例如与非ServletAPI Web框架的集成)可能需要链接构建器接口的特定实现。

让我们使用这种新语法。符合th:href属性:

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html"
   th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>

<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

有些事情需要注意:

与消息语法(#{...})的情况一样,URL基数也可以是评估另一个表达式的结果:

<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

A menu for our home page

既然我们知道如何创建链接URL,那么在我们的主页中为网站中的其他一些页面添加一个小菜单呢?

<p>Please select an option</p>
<ol>
  <li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>
  <li><a href="order/list.html" th:href="@{/order/list}">Order List</a></li>
  <li><a href="subscribe.html" th:href="@{/subscribe}">Subscribe to our Newsletter</a></li>
  <li><a href="userprofile.html" th:href="@{/userprofile}">See User Profile</a></li>
</ol>

Server root relative URLs

可以使用其他语法来创建服务器根相对(而不是上下文根相对)URL,以便链接到同一服务器中的不同上下文。这些URL将被指定为@{~/path/to/something}

4.5 Fragments

片段表达式是表示标记片段并在模板周围移动它们的简单方法。这允许我们复制它们,将它们作为参数传递给其他模板,依此类推。

最常见的用途是使用th:insert或进行片段插入th:replace(在后面的部分中将详细介绍):

<div th:insert="~{commons :: main}">...</div>

但它们可以在任何地方使用,就像任何其他变量一样:

<div th:with="frag=~{footer :: #main/text()}">
  <p th:insert="${frag}">
</div>

本教程后面有一整节专门介绍模板布局,包括对片段表达式的更深入解释。

4.6 Literals

Text literals

文本文字只是在单引号之间指定的字符串。它们可以包含任何字符,但您应该使用它们中的任何单引号\'。

<p>
  Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>

Number literals

数字文字只是:数字。

<p>The year is <span th:text="2013">1492</span>.</p>
<p>In two years, it will be <span th:text="2013 + 2">1494</span>.</p>

Boolean literals

布尔文字是true和false。例如:

<div th:if="${user.isAdmin()} == false"> ...

在这个例子中,它== false被写在大括号外面,因此Thymeleaf会照顾它。如果它是在大括号内写的,那么它将由OGNL / SpringEL引擎负责:

<div th:if="${user.isAdmin() == false}"> ...

The null literal

该null文本也可用于:

<div th:if="${variable.something} == null"> ...

Literal tokens

实际上,数字,布尔和空文字是文字标记的特例。

这些令牌允许在标准表达式中进行一些简化。它们的工作方式与文本文字('...')完全相同,但它们只允许使用字母(A-Z和a-z),数字(0-9),括号([和]),点(.),连字符(-)和下划线( _ )。所以没有空格,没有逗号等。

好的部分?令牌不需要任何围绕它们的引号。所以我们可以这样做:

<div th:class="content">...</div>

代替:

<div th:class="'content'">...</div>

4.7Appending texts

文本,无论是文字还是评估变量或消息表达式的结果,都可以使用+运算符轻松附加:

<span th:text="'The name of the user is ' + ${user.name}">

4.8Literal substitutions

文字替换允许轻松格式化包含变量值的字符串,而无需附加文字'...' + '...'。

这些替换必须用竖线(|)包围,如:

<span th:text="|Welcome to our application, ${user.name}!|">

这相当于:

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

文字替换可以与其他类型的表达相结合:

<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">

唯一的变量/消息表达式(${...},* {...},#{...})被允许内部|...|字面取代。没有其他文字('...'),布尔/数字标记,条件表达式等。

4.9Arithmetic operations

一些算术运算也可用 :+ ,- ,* ,/和%。

<div th:with="isEven=(${prodStat.count} % 2 == 0)">

请注意,这些运算符也可以应用于OGNL变量表达式本身(在这种情况下,将由OGNL而不是Thymeleaf标准表达式引擎执行):

<div th:with="isEven=${prodStat.count % 2 == 0}">

请注意,其中一些运算符存在文本别名:div(/),mod(%)。

4.10Comparators and Equality

在表达式中的值可以与进行比较>,<,>=和<=符号,以及==和!=运营商可以被用来检查是否相等(或缺乏)。请注意,XML确定不应在属性值中使用<和>符号,因此应将它们替换为<和>。

<div th:if="${prodStat.count} &gt; 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">

一个更简单的替代方法可能是使用某些运算符存在的文本别名:gt(>),lt(<),ge(>=),le(<=),not(!)。还eq(==),neq/ ne(!=)。

4.11Conditional expressions

条件表达式仅用于评估两个表达式中的一个,具体取决于评估条件的结果(它本身是另一个表达式)。

让我们来看一个例子片段(引入另一个属性修改器,th:class):

<tr th:class="${row.even}? 'even' : 'odd'">
  ...
</tr>

条件表达式(condition,then和else)的所有三个部分本身都是表达式,这意味着它们可以是变量(${...},* {...}),消息(#{...}),URL(@{...})或文字('...')。

条件表达式也可以使用括号嵌套:

<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
  ...
</tr>

也可以省略其他表达式,在这种情况下,如果条件为false,则返回null值:

<tr th:class="${row.even}? 'alt'">
  ...
</tr>

4.12Default expressions (Elvis operator)

一个默认的表情是一种特殊的条件值的没有那么一部分。它等同于某些语言(如Groovy)中存在的Elvis运算符,允许您指定两个表达式:如果它不计算为null,则使用第一个表达式,但如果确实如此,则使用第二个表达式。

让我们在用户个人资料页面中看到它:

<div th:object="${session.user}">
  ...
  <p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>

正如您所看到的,运算符是?:,并且我们在此处使用它来指定名称的默认值(在这种情况下为文字值),仅当评估结果*{age}为null时。因此,这相当于:

<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>

与条件值一样,它们可以包含括号之间的嵌套表达式:

<p>
  Name:
  <span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>

4.13The No-Operation token

No-Operation标记由下划线符号(_ )表示。

这个标记背后的想法是指定表达式的期望结果是什么都不做,即完全就像可处理属性(例如th:text)根本不存在一样。

除了其他可能性之外,这允许开发人员使用原型文本作为默认值。例如,而不是:

<span th:text="${user.name} ?: 'no user authenticated'">...</span>

...我们可以直接使用“没有用户身份验证”作为原型文本,从设计的角度来看,这会使代码更简洁,更通用:

<span th:text="${user.name} ?: _">no user authenticated</span>

4.14Data Conversion / Formatting

Thymeleaf 为variable()和selection()表达式定义了一个双括号语法,允许我们通过配置的转换服务应用数据转换。${...}* {...}

它基本上是这样的:

<td th:text="${{user.lastAccessDate}}">...</td>

注意到那里的双支撑?:${{...}}。这指示Thymeleaf将user.lastAccessDate表达式的结果传递给转换服务,并要求它在写入结果之前执行格式化操作(转换为String)。

假设它user.lastAccessDate是类型java.util.Calendar,如果已经注册了转换服务(实现IStandardConversionService)并且包含有效转换Calendar -> String,则将应用它。

IStandardConversionService(StandardConversionService类)的默认实现只是.toString()在转换为的任何对象上执行String。有关如何注册自定义转换服务实现的更多信息,请查看“ 更多配置”部分。

官方thymeleaf-spring3和thymeleaf-spring4集成软件包的透明集成了Spring自己Thymeleaf的转换服务机制转换服务的基础设施,所以在Spring配置宣称,转换服务和格式化将进行自动获得${{...}}和*{{...}}表达。

4.15 Preprocessing

除了用于表达式处理的所有这些功能外,Thymeleaf还具有预处理表达式的功能。

预处理是在正常表达式之前完成的表达式的执行,它允许修改最终将被执行的表达式。

预处理表达式与普通表达式完全相同,但显示为双下划线符号(如${expression} )。

让我们假设我们有一个Messages_fr.properties包含OGNL表达式的i18n 条目,该表达式调用特定于语言的静态方法,如:

article.text=@myapp.translator.Translator@translateToFrench({0})

......和a Messages_es.properties equivalent:

article.text=@myapp.translator.Translator@translateToSpanish({0})

我们可以创建一个标记片段,根据语言环境评估一个表达式或另一个表达式。为此,我们将首先选择表达式(通过预处理),然后让Thymeleaf执行它:

<p th:text="${__ #{article.text('textVar')}__ }">Some text here...</p>

请注意,法语区域设置的预处理步骤将创建以下等效项:

<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>

_ 可以使用在属性中转义预处理字符串\_。