Thymeleaf 是⾯向 Web 和独⽴环境的现代服务器端 Java 模板引擎,能够处理 HTML、XML、JavaScript、CSS 甚⾄纯⽂本。
Thymeleaf 特点
简单说,Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP。与其他的模板引擎相比较,它有如下三个极吸引人的特点。
- Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 HTML 原型,然后在 HTML 标签里增加额外的属性来达到模板 + 数据的展示方式。浏览器解释 HTML 时会忽略未定义的标签属性,所以 Thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
- Thymeleaf 开箱即用的特性。它支持标准方言和 Spring 方言,可以直接套用模板实现 JSTL、 OGNL 表达式效果,避免每天套模板、改 JSTL、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
- Thymeleaf 提供 Spring 标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速地实现表单绑定、属性编辑器、国际化等功能。
常用语法
相关配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在 application.properties 中添加配置:
spring.thymeleaf.cache=false
其中,propertiesspring.thymeleaf.cache=false 是关闭 Thymeleaf 的缓存,不然在开发过程中修改页面不会立刻生效需要重启,生产可配置为 true。
所有使用 Thymeleaf 的页面必须在 HTML 标签声明 Thymeleaf:
<html xmlns:th="http://www.thymeleaf.org">
赋值和拼接:
<p th:text="${userName}">neo</p>
<span th:text="'Welcome to our application, ' + ${userName} + '!'"></span>
字符串拼接还有另外一种简洁的写法:
<span th:text="|Welcome to our application, ${userName}!|"></span>
使用 ModelMap 以 KV 的方式存储传递到页面。
@RequestMapping("/string")
public String string(ModelMap map) {
map.addAttribute("userName", "ityouknow");
return "string";
}
条件判断 If/Unless
Thymeleaf 中使用 th:if 和 th:unless 属性进行条件判断,在下面的例子中,<a>
标签只有在 th:if 中条件成立时才显示:
<a th:if="${flag == 'yes'}" th:href="@{http://favorites.ren/}"> home </a>
<a th:unless="${flag != 'no'}" th:href="@{http://www.ityouknow.com/}" >ityouknow</a>
th:unless 与 th:if 恰好相反,只有表达式中的条件不成立,才会显示其内容。
for 循环
按照键 users,传递到前端:
@RequestMapping("/list")
public String list(ModelMap map) {
map.addAttribute("users", getUserList());
return "list";
}
页面 list.html 进行数据展示:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Example If/Unless </title>
</head>
<body>
<div >
<h1>for 循环</h1>
<table>
<tr th:each="user,iterStat : ${users}">
<td th:text="${user.name}">neo</td>
<td th:text="${user.age}">6</td>
<td th:text="${user.pass}">213</td>
<td th:text="${iterStat.index}">index</td>
</tr>
</table>
</div>
</body>
</html>
iterStat 称作状态变量,属性有:
- index,当前迭代对象的 index(从 0 开始计算);
- count,当前迭代对象的 index(从 1 开始计算);
- size,被迭代对象的大小;
- current,当前迭代变量;
- even/odd,布尔值,当前循环是否是偶数/奇数(从 0 开始计算);
- first,布尔值,当前循环是否是第一个;
- last,布尔值,当前循环是否是最后一个。
URL
URL 在 Web 应用模板中占据着十分重要的地位,需要特别注意的是 Thymeleaf 对于 URL 的处理是通过语法@{...}
来处理的。如果需要 Thymeleaf 对 URL 进行渲染,那么务必使用 th:href、th:src 等属性,下面是一个例子:
<a th:href="@{http://www.ityouknow.com/{type}(type=${type})}">link1</a>
<a th:href="@{http://www.ityouknow.com/{pageId}/can-use-springcloud.html(pageId=${pageId})}">view</a>
也可以使用@{...}
设置背景:
<div th:style="'background:url(' + @{${img url}} + ');'">
几点说明:
- 上例中 URL 最后的
(pageId=${pageId})
表示将括号内的内容作为 URL 参数处理,该语法避免使用字符串拼接,大大提高了可读性; -
@{...}
表达式中可以通过{pageId}
访问 Context 中的 pageId 变量; -
@{/order}
是 Context 相关的相对路径,在渲染时会自动添加上当前 Web 应用的 Context 名字,假设 context 名字为 app,那么结果应该是/app/order
。
三目运算
三目运算及表单显示:
<input th:value="${name}"/>
<input th:value="${age gt 30 ? '中年':'年轻'}"/>
说明:在表单标签中显示内容使用:th:value;${age gt 30 ? '中年':'年轻'}
表示如果 age 大于 30 则显示中年,否则显示年轻。
- gt:great than(大于)
- ge:great equal(大于等于)
- eq:equal(等于)
- lt:less than(小于)
- le:less equal(小于等于)
- ne:not equal(不等于)
结合三目运算也可以将上面的 if else 改成这样:
<a th:if="${flag eq 'yes'}" th:href="@{http://favorites.ren/}"> favorites </a>
switch 选择
switch\case 多用于多条件判断的场景下,以性别举例:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Example switch </title>
</head>
<body>
<div >
<div th:switch="${sex}">
<p th:case="'woman'">她是一个姑娘...</p>
<p th:case="'man'">这是一个爷们!</p>
<!-- *: case的默认的选项 -->
<p th:case="*">未知性别的一个家伙。</p>
</div>
</div>
</body>
</html>
*: case的默认的选项
高阶用法
内联 [ [ ] ]
如果不想通过 th 标签而是简单地访问 model 对象数据,或是想在 javascript 代码块里访问 model 中的数据,则要使用内联的方法。
内联文本:[ [...] ] 内联文本的表示方式,使用时,必须先用在 th:inline="text/javascript/none" 激活,th:inline 可以在父级标签内使用,甚至可以作为 body 的标签。内联文本比 th:text 的代码少,不利于原型显示。
页面 inline.html(文本内联):
<div>
<h1>内联</h1>
<div th:inline="text" >
<p>Hello, [[${userName}]] !</p>
<br/>
</div>
</div>
以上代码等价于:
<div>
<h1>不使用内联</h1>
<p th:text="'Hello, ' + ${userName} + ' !'"></p>
<br/>
</div>
通过以上代码可以看出使用内联语法会更简洁一些。
如果想在脚本中使用后端传递的值,则必须使用脚本内联,脚本内联可以在 js 中取到后台传过来的参数:
<script th:inline="javascript">
var name = [[${userName}]] + ', Sebastian';
alert(name);
</script>
这段脚本的含义是在访问页面的时候,根据后端传值拼接 name 值,并以 alert 的方式弹框展示。
后端传值:
@RequestMapping("/inline")
public String inline(ModelMap map) {
map.addAttribute("userName", "neo");
return "inline";
}
页面会先跳出一个 alert 提示框,然后再展示使用内联和不使用内联的页面内容。
基本对象
Thymeleaf 包含了一些基本对象,可以用于我们的视图中,这些基本对象使用 # 开头。
-
#ctx
:上下文对象 -
#vars
:上下文变量 -
#locale
:区域对象 -
#request
:(仅 Web 环境可用)HttpServletRequest 对象 -
#response
:(仅 Web 环境可用)HttpServletResponse 对象 -
#session
:(仅 Web 环境可用)HttpSession 对象 -
#servletContext
:(仅 Web 环境可用)ServletContext 对象
Thymeleaf 在 Web 环境中,有一系列的快捷方式用于访问请求参数、会话属性等应用属性,以其中几个常用的对象作为示例来演示。
-
#request
:直接访问与当前请求关联的 javax.servlet.http.HttpServletRequest 对象; -
#session
:直接访问与当前请求关联的 javax.servlet.http.HttpSession 对象。
后台添加方法传值:
@RequestMapping("/object")
public String object(HttpServletRequest request) {
request.setAttribute("request","i am request");
request.getSession().setAttribute("session","i am session");
return "object";
}
使用 request 和 session 分别传递了一个值,再来查看页面 object.html。
<body>
<div >
<h1>基本对象</h1>
<p th:text="${#request.getAttribute('request')}">
<br/>
<p th:text="${session.session}"></p>
Established locale country: <span th:text="${#locale.country}">CN</span>.
</div>
</body>
启动项目后在浏览器中输入该网址:http://localhost:8080/object,则会出现下面的结果:
基本对象
i am request
i am session
Established locale country: CN.
第一个展示了 request 如何使用参数,第二行展示了 session 的使用,session 直接使用 .
即可获取到 session 中的值,最后展示了 locale 的用法。
内嵌变量
为了模板更加易用,Thymeleaf 还提供了一系列 Utility 对象(内置于 Context 中),可以通过 # 直接访问。
- dates:java.util.Date 的功能方法类
- calendars:类似 #dates,面向 java.util.Calendar
- numbers:格式化数字的功能方法类
- strings:字符串对象的功能类,contains、startWiths、prepending/appending 等
- objects:对 objects 的功能类操作
- bools:对布尔值求值的功能方法
- arrays:对数组的功能类方法
- lists:对 lists 的功能类方法
- sets:set 的实用方法
- maps:map 的实用方法
- …
下面用一段代码来举例说明一些常用的方法,页面是 utility.html。
1. dates
可以使用 dates 对日期格式化,创建当前时间等操作。
<!--格式化时间-->
<p th:text="${#dates.format(date, 'yyyy-MM-dd HH:mm:ss')}">neo</p>
<!--创建当前时间 精确到天-->
<p th:text="${#dates.createToday()}">neo</p>
<!--创建当前时间 精确到秒-->
<p th:text="${#dates.createNow()}">neo</p>
2. strings
strings 内置了一些对字符串经常使用的函数。
<!--判断是否为空-->
<p th:text="${#strings.isEmpty(userName)}">userName</p>
<!--判断 list 是否为空-->
<p th:text="${#strings.listIsEmpty(users)}">userName</p>
<!--输出字符串长度-->
<p th:text="${#strings.length(userName)}">userName</p>
<!--拼接字符串-->
<p th:text="${#strings.concat(userName,userName,userName)}"></p>
<!--创建自定长度的字符串-->
<p th:text="${#strings.randomAlphanumeric(count)}">userName</p>
后端传值:
@RequestMapping("/utility")
public String utility(ModelMap map) {
map.addAttribute("userName", "neo");
map.addAttribute("users", getUserList());
map.addAttribute("count", 12);
map.addAttribute("date", new Date());
return "utility";
}
启动项目后在浏览器中输入该网址:http://localhost:8080/utility,则会出现下面的结果:
内嵌变量
2018-09-26 20:49:07
Wed Sep 26 00:00:00 CST 2018
Wed Sep 26 20:49:07 CST 2018
false
[false, false, false]
3
neoneoneo
QKERBVPRHFS9
接下来总结一下 Thymeleaf 表达式。
表达式
表达式共分为以下五类。
- 变量表达式:${...}
- 选择或星号表达式:*{...}
- 文字国际化表达式:#{...}
- URL 表达式:@{...}
- 片段表达式:~{...}
变量表达式
变量表达式即 OGNL 表达式或 Spring EL 表达式(在 Spring 术语中也叫 model attributes),类似 ${session.user.name}
。
它们将以 HTML 标签的一个属性来表示:
<span th:text="${book.author.name}">
<li th:each="book : ${books}">
选择(星号)表达式
选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,类似:*{customer.name}
。
被指定的 object 由 th:object 属性定义:
<div th:object="${book}">
...
<span th:text="*{title}">...</span>
...
</div>
title 即为 book 的属性。
文字国际化表达式
文字国际化表达式允许我们从一个外部文件获取区域文字信息(.properties),用 Key 索引 Value,还可以提供一组参数(可选)。
#{main.title}
#{message.entrycreated(${entryId})}
可以在模板文件中找到这样的表达式代码:
<table>
...
<th th:text="#{header.address.city}">...</th>
<th th:text="#{header.address.country}">...</th>
...
</table>
URL 表达式
URL 表达式指的是把一个有用的上下文或回话信息添加到 URL,这个过程经常被叫做 URL 重写,比如@{/order/list}
。
- URL 还可以设置参数:
@{/order/details(id=${orderId})}
- 相对路径:
@{../documents/report}
让我们看这些表达式:
<form th:action="@{/createOrder}">
<a href="main.html" th:href="@{/main}">
片段表达式
片段表达式是 3.x 版本新增的内容。片段表达式是一种标记的片段,并将其移动到模板中的方法。片段表达式的优势是,片段可以被复制或者作为参数传递给其他模板等。
最常见的用法是使用 th:insert 或 th:replace: 插入片段:
<div th:insert="~{commons :: main}">...</div>
也可以在页面的其他位置去使用:
<div th:with="frag=~{footer :: #main/text()}">
<p th:insert="${frag}">
</div>
片段表达式可以有参数。
变量表达式和星号表达有什么区别
如果不考虑上下文的情况下,两者没有区别;星号语法是在选定对象上表达,而不是整个上下文。什么是选定对象?就是父标签的值,如下:
<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 th:object="${session.user}">
<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>
表达式支持的语法
字面(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)
所有这些特征可以被组合并嵌套:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
常用 th 标签
页面常用的 HTML 标签几乎都有 Thymeleaf 对应的 th 标签。
关键字 | 功能介绍 | 案例 |
th:id | 替换 id |
|
th:text | 文本替换 |
|
th:utext | 支持 html 的文本替换 |
|
th:object | 替换对象 |
|
th:value | 属性赋值 |
|
th:with | 变量赋值运算 |
|
th:style | 设置样式 |
|
th:onclick | 点击事件 |
|
th:each | 属性赋值 |
|
th:if | 判断条件 |
|
th:unless | 和 th:if 判断相反 |
|
th:href | 链接地址 |
|
th:switch | 多路选择 配合 th:case 使用 |
|
th:case | th:switch 的一个分支 |
|
th:fragment | 布局标签,定义一个代码片段,方便其他地方引用 |
|
th:include | 布局标签,替换内容到引入的文件 |
|
th:replace | 布局标签,替换整个标签到引入的文件 |
|
th:selected | selected 选择框 选中 |
|
th:src | 图片类地址引入 |
|
th:inline | 定义 js 脚本可以使用变量 |
|
th:action | 表单提交的地址 |
|
th:remove | 删除某个属性 |
2.body:不包含标记删除,但删除其所有的子节点; 3.tag:包含标记的删除,但不删除它的子节点; 4.all-but-first:删除所有包含标签的子节点,除了第一个。 5.none:什么也不做。这个值是有用的动态评估 |
th:attr | 设置标签属性,多个属性可以用逗号分隔 | 比如 th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少 |
还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个 th:x 属性,其生效的优先级顺序为:
include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove。
Thymeleaf 配置
我们可以通过 application.properties 文件灵活的配置 Thymeleaf 的各项特性,以下为 Thymeleaf 的配置和默认参数:
# THYMELEAF (ThymeleafAutoConfiguration)
#开启模板缓存(默认值:true)
spring.thymeleaf.cache=true
#检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
#检查模板位置是否正确(默认值:true)
spring.thymeleaf.check-template-location=true
#Content-Type的值(默认值:text/html)
spring.thymeleaf.content-type=text/html
#开启MVC Thymeleaf视图解析(默认值:true)
spring.thymeleaf.enabled=true
#模板编码
spring.thymeleaf.encoding=UTF-8
#要被排除在解析之外的视图名称列表,用逗号分隔
spring.thymeleaf.excluded-view-names=
#要运用于模板之上的模板模式。另见StandardTemplate-ModeHandlers(默认值:HTML5)
spring.thymeleaf.mode=HTML5
#在构建URL时添加到视图名称前的前缀(默认值:classpath:/templates/)
spring.thymeleaf.prefix=classpath:/templates/
#在构建URL时添加到视图名称后的后缀(默认值:.html)
spring.thymeleaf.suffix=.html
#Thymeleaf 模板解析器在解析器链中的顺序,默认情况下,它排第一位,顺序从1开始,只有在定义了额外的 TemplateResolver Bean 时才需要设置这个属性。
spring.thymeleaf.template-resolver-order=
#可解析的视图名称列表,用逗号分隔
spring.thymeleaf.view-names=
在实际项目中可以根据实际使用情况来修改。