一. 什么是Thymeleaf
Thymeleaf是面向Web和独立环境的现代服务器端Java模板引擎。
Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板 - 可以正确显示在浏览器中的HTML,也可以作为静态原型工作,从而在开发团队中进行更强大的协作。
随着Spring框架的模块,与您最喜欢的工具的集成,以及插入自己的功能的能力,Thymeleaf是现代HTML5 JVM Web开发的理想选择,尽管它可以做的更多。
好吧,我承认刚才那段是Thymeleaf官方的说明,我只不过机翻了一下。下面咱们说点人话。Thymeleaf就是jsp的高端升级版。
二. 什么情况适合使用Thymeleaf
Thymeleaf显然是一个开发页面的技术,现在各种前端技术层出不穷,比如现在主流的Vue、React、AngularJS等。很多人可能会要问,这个Thymeleaf相对于这些前端框架到底有啥优势。
其实,Thymeleaf跟那些前端框架根本不是一个类型的东西,也没有啥可比性。
Thymeleaf和老牌的jsp属于非前后分离的思路来开发的。后端通过数据渲染html的模板,渲染后模板就是个完整的html页面,将页面返回给请求方。
主流的前端框架是基于前后端分离的思路来开发的,前端页面通过ajax来调用后端的rest接口来获取数据,再通过js进行渲染页面(不管什么前端技术其实都是对js进行了封装,js依然是底层核心)。
使用前后分离主要有下面几个好处
- 因为每次请求服务器获取的数据从整个页面变成了仅仅是核心数据,加载速度明显提升。
- 前端人员和后端人员可以互相独立开发,最后在通过接口联调即可。以前是不分前端工程师、后端工程师的,现在前后分离后,才出现了这样的分类。而且现在前端技术也越来越先进。前后分离以后可以方便两条技术路线的人员各自钻研自己的技术。
- 前端页面脱离后端服务器后,可以和后端分开部署。这时就可以对前端页面的服务器进行一些专门的网络优化进一步提高访问速度。
- 后端只需要一套rest接口就可以同时服务于电脑页面、IOS客户端、安卓客户端。甚至现在还有些前端技术可以直接把前端页面打包成IOS、安卓的客户端。
- ......
说了这么多前后分离的好处,你可能就要问了,那我们为什么还要用那个看起来那么low的模板引擎呢?
为了速度。前后分离方式,前端页面通过ajax来调用后端的rest接口来获取数据,再通过js进行渲染页面。获取数据和通过js渲染页面的代码,很多时候比页面本身要多的多,而且通过js来操作dom进行渲染,稍微复杂些的页面往往就会把渲染逻辑搞的错综复杂。相信从jsp时代一路走到现在的老程序员都深知工作量是成倍的往上翻。
固然刚才列举了前后分离的种种好处,但这些好处大多数都是集中在app开发上,其他某些场景下这些好处并不明显。最典型的一个场景就是管理后台。管理后台往往对页面的花哨性要求不高,并发量也不大,而且功能往往还不少。这种场景下,前后分离技术上导致的工作量大幅度增加,人员上分离导致额外的联调成本都成了不少的负担。Thymeleaf作为模板引擎这时候优势就非常大。只需要在html原型的页面上稍微加几个标签,即可完成渲染。而且加上的标签并不影响原型页面直接通过html打开。
说了这么多,总结一下,Thymeleaf是一个供后端人员使用的,为快速开发页面而生的Java模板引擎。
三. 如何在Springboot中引入Thymeleaf
Thymeleaf作为spring官方推荐的模板引擎,在spring体系中使用异常方便。这里以gradle构建的项目为例来说明。
首先,你要先修改build.gradle引入Springboot对Thymeleaf提供的依赖包。在dependencies中增加如下配置。
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
等待gradle帮你自己下载完依赖包后,你可以看到引入的Thymeleaf的版本。
嗯?springboot1.5.7默认引用的Thymeleaf依赖包居然还是2.1.5版本。最新的Thymeleaf不是已经更新3.x版本了么。如果我想使用最新版的Thymeleaf要怎么办呢。
在build.gradle文件中,buildscript下增加配置,完整的配置如下图
ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'
等待gradle下载完成,你可以看到引入的Thymeleaf已经是最新版本了。
四. 快速入门
接下来要在项目中使用Thymeleaf了,这里用一个简单的单表查询来举个栗子。
一般来说,开发一个需要渲染数据的页面,分为三个步骤。
- 开发静态页面,即常说的模型。
- 获取数据。
- 使用数据对静态页面进行渲染。
这里我们先做第一个步骤,开发静态页面。为了简单,就不做任何css了,下面是页面的源码。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>标题</title>
</head>
<body>
<strong>标题</strong>
<form action="list.html" method="post">
<input type="hidden" name="pageNumber">
用户名:<input type="text" name="username">
<br/>
姓名:<input type="text" name="name">
<br/>
<button type="submit">提交</button> <button type="reset">重置</button>
</form>
<table>
<thead>
<tr>
<th class="am-hide-sm-only">id</th>
<th>用户名</th>
<th>姓名</th>
<th class="am-hide-sm-only">电话</th>
<th class="am-hide-sm-only">邮箱</th>
<th class="am-hide-sm-only">是否可用</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>用户名</td>
<td>姓名</td>
<td>电话</td>
<td>邮箱</td>
<td>
<span>可用</span><span>不可用</span>
</td>
<td><button>修改</button><button>删除</button></td>
</tr>
</tbody>
</table>
</body>
</html>
直接使用浏览器打开该页面,长成这样。
现在有了静态页面,该获取数据了。下面是controller层的代码。
/**
* 用户管理
*/
@Controller
@RequestMapping("/users")
public class UserController
{
@Autowired
private UserRepository userRepository;
@Value("${pageSize}")
private Integer pageSize;
/**
* 分页查询信息
*/
@RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
public String list(Model model, User user, @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber)
{
ExampleMatcher matcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
PageRequest pageRequest = new PageRequest(pageNumber, pageSize);
Page<User> page = userRepository.findAll(Example.of(user, matcher), pageRequest);
//分页查询数据
model.addAttribute("page", page);
//查询条件
model.addAttribute("user", user);
//页面标题
model.addAttribute("title", "用户管理");
//转到待渲染模板,所有模板都在templates文件夹下,users/list指templates文件夹下的users文件夹下的list.html页面。
return "users/list";
}
}
这里使用spring-data-jpa从数据库里查询到了记录并和查询条件、页面标题一起转到待渲染的模板。这里,我们将刚才的静态页面文件复制到对应的位置。如下图。
下面,我们要对该文件进行适当的改造,使之成为一个Thymeleaf模板文件。先贴上改造后的文件。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${title}">标题</title>
<meta charset="UTF-8">
</head>
<body>
<strong th:text="${title}">标题</strong>
<form th:action="@{/users}" th:object="${user}" method="post">
<input type="hidden" name="pageNumber" th:value="${page.number}">
用户名:<input type="text" name="username" th:value="*{username}">
<br/>
姓名:<input type="text" name="name" th:value="*{name}">
<br/>
<button type="submit">提交</button> <button type="reset">重置</button>
</form>
<table>
<thead>
<tr>
<th class="am-hide-sm-only">id</th>
<th>用户名</th>
<th>姓名</th>
<th class="am-hide-sm-only">电话</th>
<th class="am-hide-sm-only">邮箱</th>
<th class="am-hide-sm-only">是否可用</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${page.content}">
<td th:text="${user.id}">id</td>
<td th:text="${user.username}">用户名</td>
<td th:text="${user.name}">姓名</td>
<td th:text="${user.phone}">电话</td>
<td th:text="${user.email}">邮箱</td>
<td th:switch="${user.enabled}">
<span th:case="true">可用</span><span th:case="false">不可用</span>
</td>
<td><button>修改</button><button>删除</button></td>
</tr>
</tbody>
</table>
</body>
</html>
可以看到,我们对html代码的结构丝毫未改动,只是在一些标签里面添加了th:xxx="yyy"
的属性。
我们重新使用浏览器打开Thymeleaf改造过的html文件。发现虽然我们添加了那么多th:xxx="yyy"
的标签,但是,页面居然和之前一模一样。
下面我们启动服务,通过controller定义的那个url来访问渲染后的页面。
同一个文件,浏览器直接打开就是原型,服务器渲染后打开就是真实的功能页面。
这里我们就可以看出Thymeleaf的一个核心功能,就是将其逻辑注入到模板文件中,不会影响模板被用作设计原型。做到了原型即页面。
Thymeleaf的核心语法就是th:xxx="yyy"
,即设置html标签中xxx属性的值为yyy对应的值。
五. 设置属性
我们先来说一说th:xxx
的部分,即设置属性。
Thymeleaf的核心功能就是通过在html标签里面追加属性。可以设置的属性非常多,详细的可以参考 Thymeleaf所有属性的还没发文档。这里我们就挑选些常用的举几个例子,其他大家可以举一反三推断出用法。
th:object="yyy"
将对象作为一个范围内可用的变量。一般和选择表达式*{zzz}
配合使用,选择表达式后面会讲到。th:text="yyy"
这个属性可以添加到几乎所有分为头尾两部分<></>
的html标签中,如<title></title>
、<td><td/>
等。th:text="yyy"
的作用是把表达式yyy对应的值添加到标签的中间。
如<td th:text="user">用户名</td>
渲染后就是<td>user<td/>
。th:value="${title}"
这个属性一般和<input />
标签搭配使用,用来设置<input />
标签的value
值。
如<input th:value="username" />
渲染后就是<input value="username" />
。
六. 表达式
下面我们再来对这些th:xxx="yyy"
中"yyy"
的部分进行讲解。这个yyy我们一般称之为表达式。
Thymeleaf里面表达式主要有以下几种。
-
${yyy}
变量表达式,用来获取上下文对象里面的值(controller返回的model)。还是以上面的例子来说明,如果我想要取到page
对象中的number
属性,使用${page.number}
即可。
-
#{yyy}
消息表达式,根据消息的key来获取消息内容。一般是用来做国际化用的。 -
*{yyy}
选择表达式,跟变量表达式用法差不多,但变量表达式是获取上下文里的对象,选择表达式是获取一个选择的对象。
选择表达式一般和th:object=
标签配合使用,还是以上面的例子来说明。
先用th:object="${user}"
选择了上下文中的user
对象,下面想使用user
对象的username
属性时,直接使用*{username}
就可以了。
你可能想要问,我直接使用${user.username}
不是一样可以找到user
对象的username
属性么,为什么还要再搞个选择表达式?
因为${user.username}
是先从下上文找到user
,对象,再从user
对象里找到username
属性;而*{username}
是直接从user
对象里找到username
属性。当需要从一个对象里获取很多属性的时候,使用选择表达式可以提高效率。
-
@{yyy}
链接表达式 设置超链接时用的表达式,一般和th:action
、th:href
配合使用。
~{page :: fragment}
分段表达式,主要用作公共模块的复用,一般和th:insert
、th:replace
搭配使用。以后讲模块复用的时候再细说。yyy
文字。可以为字符串、数字、布尔、null。如<td th:text="user">用户名</td>
渲染后为<td>user</td>
。_
无操作。下划线是thymeleaf表达式的特殊字符,如果表达式就一个下划线,则什么也不做。例如<td th:text="_">用户名</td>
渲染后依然是<td>用户名</td>
。
七. 迭代器
我们在渲染页面时,经常需要对一个list进行循环处理,最典型的场景就是使用表格展示多条数据。这时,就需要使用到thymeleaf的迭代器th:each
。
<tr th:each="user : ${users}">
<td th:text="${user.id}">id</td>
<td th:text="${user.username}">用户名</td>
</tr>
在这个例子中,users
是一个list,通过迭代器th:each
对其进行遍历,每次迭代获取到的对象为user
。在th:each
属性的对应的标签之间<tr th:each="user : ${users}">...</tr>
,为user
对象的有效范围。
有时候,我们还想要知道迭代器的一些状态属性,如总数,当前索引等。可以通过如下方法获取。
<tr th:each="user,stat : ${users}">
<td th:text="${stat.index}">index</td>
<td th:text="${user.id}">id</td>
<td th:text="${user.username}">用户名</td>
</tr>
th:each=""
的第二个变量stat
,就是迭代器的状态变量,从这个状态变量里面可以获取到很多我们想要的属性,主要有下面这些。
-
stat.index
当前对象在list中的索引。从0开始。 -
stat.count
和index
差不多,也是当前对象在list中的索引,不过是从1开始。 -
stat.size
迭代器中元素的总数。 -
stat.current
当前迭代的对象。 -
stat.even
当前迭代的索引是否是奇数,索引指stat.index
-
stat.odd
当前迭代的索引是否是偶数,索引指stat.index
-
stat.first
当前迭代的对象是否是迭代器中的第一个。 -
stat.last
当前迭代的对象是否是迭代器中的最后一个。
八. 条件语句
th:if="boolean"
th:if
的表达式需为boolean值。如果为true,则标签显示,如果为false,则标签不显示。
th:unless="boolean"
th:unless
和th:if
相反,表达式也需为boolean值。如果为true,则标签不显示,如果为false,则标签显示。
<span th:if="${stat.odd}">偶</span>
<span th:unless="${stat.odd}">奇</span>
th:swtich
一般和 th:case
结合使用 。和java语言中的swtich case
语法用法类似。
<td th:switch="${user.enabled}">
<span th:case="true">可用</span><span th:case="false">不可用</span>
</td>
九. 工具类
Thymeleaf提供了一些工具类,这里举个简单的例子展示下用法,其他详细的可以查看Thymeleaf工具类官方文档。
#lists
数组工具类
总共有<span th:text="${#lists.size(page.content)}">1</span>条记录