浅谈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:block 或 th-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 融入到到 html ,javaScript , css 中。

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

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>