四、标准表达语法
我们将在杂货店虚拟 Store 的开发中稍作 Rest,以了解 Thymeleaf Standard 方言最重要的部分之一:Thymeleaf Standard Expression 语法。
我们已经看到了用这种语法表示的两种有效属性值:消息和变量表达式:
<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:
-
变量表达式:
${...}
- 选择变量表达式:
*{...}
- 讯息表达:
#{...}
- 链接 URL 表达式:
@{...}
- 片段表达式:
~{...}
- 选择变量表达式:
- Literals
-
LiteralsLiterals:
'one text'
,'Another one!'
,…- 数字 Literals:
0
,34
,3.0
,12.3
,… - 布尔 Literals:
true
,false
- 空 Literals:
null
- Literals 标记:
one
,sometext
,main
,…
- 数字 Literals:
- Text operations:
-
字符串串联:
+
- Literals 替换:
|The name is ${name}|
- Literals 替换:
- Arithmetic operations:
-
二进制运算符:
+
,-
,*
,/
,%
- 减号(一元运算符):
-
- 减号(一元运算符):
- Boolean operations:
-
二进制运算符:
and
,or
- 布尔取反(一元运算符):
!
,not
- 布尔取反(一元运算符):
- 比较和equal:
-
比较器:
>
,<
,>=
,<=
(gt
,lt
,ge
,le
)- 等于运算符:
==
,!=
(eq
,ne
)
- 等于运算符:
- Conditional operators:
-
如果-则:
(if) ? (then)
- 如果-则-其他:
(if) ? (then) : (else)
- 默认值:
(value) ?: (defaultvalue)
- 如果-则-其他:
- Special tokens:
- 无操作:
_
所有这些功能都可以组合和嵌套:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
4.1 Messages
众所周知,#{...}
消息表达式使我们可以链接此链接:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
…to this:
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 文档中指定的数字和日期。
为了为我们的参数指定一个值,并给定一个名为user
的 HTTP 会话属性,我们可以:
<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
请注意,此处使用
th:utext
表示格式化的消息将不会被转义。本示例假定user.name
已被转义。
可以指定几个参数,以逗号分隔。
消息密钥本身可以来自变量:
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>
4.2 Variables
我们已经提到过${...}
表达式实际上是在上下文中包含的变量 Map 上执行的 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('-')}
表达式基本对象
在上下文变量上评估 OGNL 表达式时,某些对象可用于表达式,以提高灵 Active。这些对象将以#
符号开头(根据 OGNL 标准)被引用:
#ctx
:上下文对象。#vars:
上下文变量。#locale
:上下文语言环境。#request
:(仅在 Web 上下文中)HttpServletRequest
对象。#response
:(仅在 Web 上下文中)HttpServletResponse
对象。#session
:(仅在 Web 上下文中)HttpSession
对象。#servletContext
:(仅在 Web 上下文中)ServletContext
对象。
因此,我们可以这样做:
Established locale country: <span th:text="${#locale.country}">US</span>.
您可以在Appendix A中阅读这些对象的完整参考。
表达式 Util 对象
除了这些基本对象之外,Thymeleaf 将为我们提供一组 Util 对象,这些对象将帮助我们在表达式中执行常见任务。
#execInfo
:有关正在处理的模板的信息。#messages
:用于获取变量表达式内的外部化消息的方法,与使用#\ {…}语法所获得的方法相同。#uris
:转义部分 URL/URI 的方法#conversions
:用于执行配置的转换服务(如果有)的方法。#dates
:用于java.util.Date
对象的方法:格式化,组件提取等。#calendars
:类似于#dates
,但适用于java.util.Calendar
个对象。#numbers
:格式化数字对象的方法。#strings
:String
个对象的方法:包含,startsWith,前置/附加等。#objects
:一般对象的方法。#bools
:布尔值评估的方法。#arrays
:数组的方法。#lists
:列表方法。#sets
:集合的方法。#maps
:Map 方法。#aggregates
:用于在数组或集合上创建聚合的方法。#ids
:用于处理可能重复的 id 属性的方法(例如,作为迭代的结果)。
您可以在Appendix B中检查这些 Util 对象提供的功能。
重新格式化首页中的日期
现在我们知道了这些 Util 对象,可以使用它们来更改在主页上显示日期的方式。而不是在我们的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 选择内容的表达方式(星号语法)
变量表达式不仅可以写为${...}
,还可以写成*{...}
。
但是有一个重要的区别:星号语法在选定对象而不是整个上下文上评估表达式。也就是说,只要没有选定的对象,美元和星号的语法就完全一样。
什么是选定对象?使用th:object
属性的表达式的结果。让我们在用户 Profile(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.4 链接网址
由于 URL 的重要性,URL 是 Web 应用程序模板中的一等公民,并且* Thymeleaf Standard Dialect *具有特殊的语法,即@
语法:@{...}
URL 有不同类型:
- 绝对网址:
http://www.thymeleaf.org
- 相对 URL,可以是:
-
相对页面:
user/login.html
- 上下文相关:
/itemdetails?id=3
(服务器中的上下文名称将自动添加) - 相对服务器:
~/billing/processInvoice
(允许在同一服务器中的另一个上下文(=应用程序)中调用 URL。 - 相对协议网址:
//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>
这里要注意一些事情:
th:href
是修饰符属性:处理后,它将计算要使用的链接 URL,并将该值设置为<a>
标签的href
属性。- 我们被允许对 URL 参数使用表达式(如
orderId=${o.id}
所示)。所需的 URL 参数编码操作也将自动执行。 - 如果需要几个参数,这些参数将以逗号分隔:
@{/order/process(execId=${execId},execType='FAST')}
- URL 路径中也允许使用变量模板:
@{/order/{orderId}/details(orderId=${orderId})}
- 以
/
开头的相对 URL(例如/order/details
)将自动以应用程序上下文名称作为前缀。 - 如果未启用 Cookie 或尚不知道,则可能会将
";jsessionid=..."
后缀添加到相对 URL 中,以便保留会话。这称为* URL 重写*,Thymeleaf 允许您通过使用 Servlet API 中的response.encodeURL(...)
机制为每个 URL 插入自己的重写过滤器。 th:href
属性允许我们(可选)在模板中具有可使用的静态href
属性,因此,当直接打开原型进行原型设计时,模板链接仍可被浏览器导航。
与消息语法(#{...}
)一样,URL 基也可以是求值另一个表达式的结果:
<a th:href="@{${url}(orderId=${o.id})}">view</a>
<a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>
主页的菜单
现在,我们知道了如何创建链接 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>
服务器根目录相对 URL
可以使用其他语法来创建相对于服务器根目录的 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
文本 Literals 只是在单引号之间指定的字符串。它们可以包含任何字符,但是您应该使用\'
对其内的任何单引号进行转义。
<p>
Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>
Number literals
数字 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
布尔 Literals 是true
和false
。例如:
<div th:if="${user.isAdmin()} == false"> ...
在此示例中,== false
写在大括号外,因此 Thymeleaf 负责处理。如果将其写在花括号内,则 OGNL/SpringEL 引擎应负责:
<div th:if="${user.isAdmin() == false}"> ...
空 Literals
null
Literals 也可以使用:
<div th:if="${variable.something} == null"> ...
Literal tokens
实际上,数字,布尔值和 nullLiterals 是* literal tokens *的一种特殊情况。
这些标记允许在标准表达式中进行一些简化。它们的工作方式与文本 Literals('...'
)完全相同,但是它们仅允许使用字母(A-Z
和a-z
),数字(0-9
),方括号([
和]
),点(.
),连字符(-
)和下划线(_
)。因此,没有空格,没有逗号等。
好的部分?令牌不需要任何引号。因此,我们可以这样做:
<div th:class="content">...</div>
instead of:
<div th:class="'content'">...</div>
4.7 附加 Literals
文本,无论是 Literals 还是评估变量或消息表达式的结果,都可以使用+
运算符轻松附加:
<span th:text="'The name of the user is ' + ${user.name}">
4.8Literals 替代
Literals 替换可以轻松格式化包含变量值的字符串,而无需在 Literals 后面加上'...' + '...'
。
这些替换项必须用竖线(|
)包围,例如:
<span th:text="|Welcome to our application, ${user.name}!|">
等效于:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
Literals 替换可以与其他类型的表达式结合使用:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">
|...|
Literals 替换内仅允许变量/消息表达式(${...}
,*{...}
,#{...}
)。没有其他 Literals('...'
),布尔/数字标记,条件表达式等。
4.9 算术运算
一些算术运算也可用:+
,-
,*
,/
和%
。
<div th:with="isEven=(${prodStat.count} % 2 == 0)">
请注意,这些运算符也可以在 OGNL 变量表达式内部应用(在这种情况下,将由 OGNL 代替 Thymeleaf 标准表达式引擎执行):
<div th:with="isEven=${prodStat.count % 2 == 0}">
请注意,其中一些运算符存在文本别名:div
(/
),mod
(%
)。
4.10 比较器和equal
表达式中的值可以与>
,<
,>=
和<=
符号进行比较,并且==
和!=
运算符可以用于检查是否相等(或是否相等)。请注意,XML 规定<
和>
符号不应在属性值中使用,因此应将它们替换为<
和>
。
<div th:if="${prodStat.count} > 1">
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
一个更简单的替代方法可能是使用以下某些运算符存在的文本别名:gt
(>
),lt
(<
),ge
(>=
),le
(<=
),not
(!
)。也是eq
(==
),neq
/ne
(!=
)。
4.11 条件表达式
条件表达式旨在仅根据评估条件的结果来评估两个表达式之一(本身就是另一个表达式)。
让我们看一个示例片段(引入另一个* attribute 修饰符* th:class
):
<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>
条件表达式的所有三个部分(condition
,then
和else
)本身都是表达式,这意味着它们可以是变量(${...}
,*{...}
),消息(#{...}
),URL(@{...}
)或 Literals('...'
)。
也可以使用括号嵌套条件表达式:
<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
...
</tr>
其他表达式也可以省略,在这种情况下,如果条件为 false,则返回 null 值:
<tr th:class="${row.even}? 'alt'">
...
</tr>
4.12 默认表达式(Elvis 运算符)
默认表达式是一种特殊的条件值,没有* then 部分。它等效于 Groovy 之类的某些语言中的 Elvis 运算符*,可让您指定两个表达式:如果第一个表达式的计算结果不为 null,则使用第一个表达式,但是如果第二个表达式使用,则使用第二个表达式。
让我们在用户 Profile 页面中看到它的实际效果:
<div th:object="${session.user}">
...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p>
</div>
如您所见,运算符为?:
,并且仅当求值*{age}
的结果为 null 时,才在此处使用其指定名称的默认值(在这种情况下为 Literals 值)。因此,这等效于:
<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.13 The No-Operation token
No-Operation 令牌由下划线符号(_
)表示。
此令牌背后的想法是指定表达式的期望结果是“不执行任何操作”,即完全像处理属性(例如th:text
)根本不存在一样。
除其他可能性外,这还使开发人员可以将原型文本用作默认值。例如,代替:
<span th:text="${user.name} ?: 'no user authenticated'">...</span>
…我们可以直接使用*’no user authenticated’*作为原型文本,这样从设计的角度来看,代码既简洁又通用:
<span th:text="${user.name} ?: _">no user authenticated</span>
4.14 数据转换/格式化
Thymeleaf 为变量(${...}
)和选择(*{...}
)表达式定义了双括号语法,该语法使我们能够通过配置的转换服务来应用数据转换。
它基本上是这样的:
<td th:text="${{user.lastAccessDate}}">...</td>
注意到那里有双括号了吗:${{...}}
。指示 Thymeleaf 将user.lastAccessDate
表达式的结果传递给转换服务,并要求它执行 格式化操作 (转换为String
),然后再写入结果。
假设user.lastAccessDate
的类型为java.util.Calendar
,如果已注册转换服务(IStandardConversionService
的实现)并包含Calendar -> String
的有效转换,则将应用它。
IStandardConversionService
(StandardConversionService
类)的默认实现只是对转换为String
的任何对象执行.toString()
。有关如何注册自定义转换服务实现的更多信息,请查看有关配置的更多信息部分。
官方的 thymeleaf-spring3 和 thymeleaf-spring4 集成软件包将 Thymeleaf 的转换服务机制与 Spring 自己的* Conversion Service *基础结构透明地集成在一起,以便在 Spring 配置中声明的转换服务和格式化程序将自动提供给
${{...}}
和*{{...}}
表达式。
4.15 Preprocessing
除了用于表达式处理的所有这些功能之外,Thymeleaf 还具有预处理表达式的功能。
预处理是在普通表达式之前执行的表达式的执行,该表达式允许修改最终将要执行的表达式。
预处理表达式与普通表达式完全一样,但是会出现双下划线符号(例如__${expression}__
)。
假设我们有一个 i18n Messages_fr.properties
条目,其中包含一个 OGNL 表达式,该表达式调用特定于语言的静态方法,例如:
article.text=@myapp.translator.Translator@translateToFrench({0})
…和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>
可以使用\_\_
在属性中对预处理字符串__
进行转义。
最新评论
https://pan.baidu.com/s/1q3bnTncIACKoTZFxvx7BQw?pwd=ii7n
RabbitMQ精讲,项目驱动落地,分布式事务拔高 有吗?
Spring Cloud Alibaba 微服务架构实战 https://pan.baidu.com/s/1jF5voFRoeF0lYAzAPBWSbw?pwd=chqk
命令: nload
真是个良心站点哇,大公无私,爱了爱了
还可以直接搞一张映射表,存 uid | time | source_index, 第一次直接查对应的 time 选出前100, 第二次直接用 CompleteFuture 去分别用 source_in
干得漂亮,多个朋友堵条路
2021.2.2版本的不适用吧