一. 什么是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標簽里面追加屬性??梢栽O置的屬性非常多,詳細的可以參考 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>條記錄