浅谈Thymeleaf

Thymeleaf 是一个跟 FreeMarker、Velocity 类似的模板引擎,是 Spring 所推荐的模板技术 。

Thymeleaf 引以为傲的特性是 自然模板(natural templating) ,即由于主要语法基于标签属性等,使得模板可以直接在静态环境显示正常。

本文内容基于 Thymeleaf3.0

本文内容只基于 html模板 ,虽然Thymeleaf 提供了文本模板模式(Textual template modes),使其可以作用于 .js.css ,但是这里不会涉及。

方言和属性修改器

方言

Thymeleaf 集成包定义了一种 标准方言SpringStandard Dialect ),其兼容了 Spring EL ,而 Spring EL 基本和 OGNL 相同,所以使用起来十分友好。

属性修改器

标准方言的多数处理器都是属性处理器,Thymeleaf 可以使用两种属性修改器语法 :

属性修改器语法 说明 示例
th:* 命名空间语法。 <input th:value="${username}">
data-{prefix}-{name} html5自定义属性语法。 <input data-th-value="${username}">

一般来说,使用这种语法是可以直接在浏览器打开的(浏览器会忽略不可识别的属性),所以 Thymeleaf 是 自然模板 。但实际上 th:* 并不符合 HTML5 规范,IDE还是会提示错误。可以使用引入以下 xmlns 命名空间定义:

1
<html xmlns:th="http://www.thymeleaf.org">

可以看出 data-th-* 相较 th:* 对 HTML5 更加友好,两种方式都适用HTML5的各种属性。

但实际上 data-th-* 只是对 th:* 的补充,官方示例推崇 th:* 语法。

其实还有另一种语法:{prefix}:{name}{prefix}-{name} ,其可以指定自定义标签。

例如: 可以使用 th:blockth-block 元素,来表示自定义标签。

标准表达式语法

在属性修改器中可以使用一些标准表达式是语法。

在大括号外面的数据由Thymeleaf来处理。而在大括号内写的,将由OGNL / SpringEL引擎负责。

涉及一览 说明
${...} 变量表达式
*{...} 选择变量表达式
#{...} 消息表达式
@{...} URL 链接表达式

变量表达式

通过 ${...} 可以从 context 中获取变量。

使用 . 或者 [] 访问属性,相当于调用 getter

1
2
3
${person.father.name}

${person['father']['name']}

对于 Map对象,如下使用,相当于 get(...)

1
2
3
${countriesByCode.ES}

${personsByName['Stephen Zucchini'].age}

对于 数组 或 集合 对象,可以使用索引访问。

1
${personsArray[0].name}

甚至可以直接调用方法。

1
2
3
${person.createCompleteName()}

${person.createCompleteNameWithSeparator('-')}

实用对象(Expression Utility)

Thymeleaf内置了许多实用对象,这里使用内置工具类格式化日期类型。

附录B:表达式实用程序对象

1
2
3
<p>
Today is: <span th:text="${#calendars.format(today,'yyyy-MM-dd')}">13 May 2018</span>
</p>

选择表达式

*{...} 一般配合 th:object 使用,优先从其选定的对象中取值,当没有th:object 时,其作用等同${...}

当多次从一个对象中获取属性时,可以如下使用:

1
2
3
4
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
</div>

其等同于以下写法:

1
2
3
4
<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
</div>

消息表达式

使用 #{...} 可以直接使用消息(message),其一般用于国际化。

配置国际化文件 classpath:i18n/messages_zh_CN.properties

1
home.welcome=欢迎来到我们的杂货店!

在模板中使用消息。

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

URL链接表达式

使用 @{...} 可以用于处理 URL链接。

1
2
// 服务器中的上下文名称将自动添加
@{/itemdetails}

片段表达式

使用 ~{...} 可以将模板片段作为变量使用,这个会在谈Thymeleaf 布局( Layout )时再介绍 。

标准表达式支持的语法

字面(Literals)

其实这里是介绍支持的各种数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 文字 -->
<p>
Now you are looking at a <span th:text="'working web application'">template file</span>.
</p>

<!-- 数字 -->
<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>

<!-- 布尔 -->
<!-- Thymeleaf处理 -->
<div th:if="${user.isAdmin()} == false">
<!-- OGNL / SpringEL引擎处理 -->
<div th:if="${user.isAdmin() == false}">

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

<!-- Token -->
// TODO

文本操作(Text operations)

转义文本 与 非转义文本

th:text 会转义其中的内容,而 th:utext 则不会转义,这更使用于包含标签 的文本。

1
home.welcome=Welcome to our <b>fantastic</b> grocery store!
1
2
3
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
<!-- 解析结果如下 -->
<p>Welcome to our &lt;b&gt;fantastic&lt;/b&gt; grocery store!</p>
1
2
3
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
<!-- 解析结果如下 -->
<p>Welcome to our <b>fantastic</b> grocery store!</p>

附加文本(Appending texts)

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

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

字面替换(Literal substitutions)

当需要将变量值包含到字符串中,可以使用字面替换,而不是用 + 附加文本。

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

运算

算术运算

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

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

比较运算

在标签语言中需要对 <> 进行转义,而 Thymeleaf 还支持别名。

比较运算符 别名
> gt
< lt
>= ge
<= le
! not
== eq
!= neq / ne
1
2
3
4
<div th:if="${prodStat.count} gt 1">

<!-- 等同于 -->
<div th:if="${prodStat.count} &gt; 1">

条件运算

属性修改器中也可以使用三目运算符: condition ? then : else

else 可以省略,即 condition ? then ,若条件为 false ,则返回 null值。

1
2
3
<tr th:class="${row.first}? 'first' : 'even'">
...
</tr>

默认值(Elvis运算符)

Elvis运算符 类似 条件运算 的变种,可以在取值为 null 时,提供默认值。

1
2
3
4
<p>Age: <span th:text="{age}?:'(no age specified)'">27</span>.</p>

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

无操作令牌

希望原文本为默认值,可以使用 _ 代替。

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

<!-- 相当于 -->
<span th:text="${user.name} ?: 'no user authenticated'">no user authenticated</span>

迭代

当需要对集合进行循环遍历是可以使用 th:each

不显示设置迭代状态变量,则默认为 迭代变量 + Stat ,即 prodStat

1
2
3
4
5
6
7
8
9
10
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
</table>

可以显示设置迭代状态变量 iterStat ,如下可以控制奇数行样式。

1
2
3
4
5
6
7
8
9
10
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr th:each="item, iterStat : ${list}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${item.name}">Onions</td>
<td th:text="${item.price}">2.41</td>
</tr>
</table>

迭代状态变量

状态变量在 th:each 属性中定义,包含以下数据。

迭代变量属性 说明
index 当前迭代索引,从0开始。
count 当前迭代索引,从1开始。
size 迭代变量中元素的总量。
current 每次迭代的iter变量
even / odd 当前迭代是否是偶数、奇数,布尔属性。
first 当前迭代是否是第一个迭代。
last 当前迭代是否是最后一次。

条件

注意: 条件不仅判断 布尔值,还有如下扩展。

  • 如果value不为null,且为以下值则视为 true ,反之视为 false
    • 如果value是布尔值,且为true。
    • 如果value是数字且不为零。
    • 如果value是一个字符且不为零。
    • 如果value是String并且不是“false”,“off”或“no”。
    • 如果value不是布尔值,数字,字符或字符串。
  • 如果value为null,则th:if将计算为 false

th:if

1
2
<a href="comments.html"  
th:if="${#lists.isEmpty(prod.comments)}">view</a>

th:unless

th:unless 意为除非,与 if 相反, 比使用 not 更加友好。

1
2
3
4
5
6
<a href="comments.html"  
th:unless="${#lists.isEmpty(prod.comments)}">view</a>

<!-- 等同于 -->
<a href="comments.html"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>

注释

可以看出在注释方面,Thymeleaf 也具有很小的侵入性,完美融入HTML。

涉及一览 说明
<!-- -- > hmtl注释
<!--/* */-- > 解析器级注释
<!--/*/ /*/-- > 原型注释

标准HTML / XML注释

Thymeleaf 兼容 html注释,其内容不会被解析。

1
2
3
4
<!-- User info follows -->
<div th:text="${...}">
...
</div>

解析器级注释

在解析的,直接删除 <!--/* */-- > 及其中内容。

1
<!--/* This code will be removed at Thymeleaf parsing time! */-->

原型注释

在解析完成后,删除 <!--/*/ /*/-- > ,释放其中内容。

1
2
3
4
5
<!--/*/
<div th:text="${...}">
...
</div>
/*/-->

元素处理器

Thymeleaf标准方言中包含了唯一的元素处理器(不是属性),即th:block

th:block是一个纯粹的属性容器,允许模板开发人员指定他们想要的任何属性。Thymeleaf将执行这些属性,然后简单地使块,而不使它的内容消失。

因此,在创建 <tr> 每个元素需要多个迭代表时,它可能很有用,且当与仅原型注释块结合使用时尤其有用:

1
2
3
4
5
6
7
8
9
10
11
<table>
<!--/*/ <th:block th:each="user : ${users}"> /*/-->
<tr>
<td th:text="${user.login}">...</td>
<td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td>
</tr>
<!--/*/ </th:block> /*/-->
</table>

内联

在 html模板中,不直接基于标签,而将 Thymeleaf 融入到到 htmljavaScriptcss 中。

涉及一览 说明
[[]] 转义表达式
[()] 非转义表达式
/*[[]]*/ 自然模板

HTML 内联

表达内联

但在某些情况下我们可能更喜欢将表达式直接写入HTML文本,这是可以使用内联表达式 [[]][()]

注意 :虽然看上去会更简洁,但 Thymeleaf表达式 会直接展示在静态环境中,不利于原型设计。

1
2
3
4
<p>Hello, [[${session.user.name}]]!</p>

<!-- 等同于 -->
<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>
1
2
3
4
<p>Hello, [(${session.user.name})]!</p>

<!-- 等同于 -->
<p>Hello, <span th:utext="${session.user.name}">Sebastian</span>!</p>

禁用内联

可以看出 [[]] 标记会被 Thymeleaf 解析,在某些情况下并不好,这时可以使用 th:inline="none" 禁用此机制。

1
<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

JavaScript 内联

JavaScript内联中也支持 [[]][()] ,但都不能确保取值一定是合适的合法的 js变量 ,所以推荐使用 自然模板 ,即 /*[[]]*/ 。自然模板也支持默认值,所以对静态环境更加友好。

必须使用 th:inline="javascript" 以下方式明确启用JavaScript 内联模式:

1
2
3
4
5
6
<script th:inline="javascript">
...
var username1 = /*[[${session.user.name}]]*/ ;
var username2 = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
...
</script>

CSS 内联

CSS内联中同样支持 [[]][()] ,但更推荐自然模板 /*[[]]*/

必须使用 th:inline="css" 以下方式明确启用css内联模式:

1
2
3
4
5
<style th:inline="css">
.main\ elems {
text-align: /*[[${align}]]*/ left;
}
</style>