Thymeleaf - 使用方法及國際化(超詳細)
Thymeleaf簡介
Thymeleaf是一個和Velocity、FreeMarker 類似的模板引擎,它在有網絡和無網絡的環境下皆可運行。因為它支持html原型,在html的標簽里增加了額外的屬性來達到模板+數據的展示方式。瀏覽器解釋html時會忽略未定義的標簽屬性,所以thymeleaf的模板可以靜態地運行。當有數據返回到頁面時,Thymeleaf標簽會動態地替換掉靜態內容,使頁面動態顯示。
它與SpringBoot完美結合,SpringBoot提供了Thymeleaf的默認配置,並且為Thymeleaf設置了視圖解析器。它可以快速實現表單綁定、屬性編輯器、國際化等功能。
在SpringBoot中的配置
1、添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 1
- 2
- 3
- 4
2、創建模板文件夾
SpringBoot自動為Thymealf注冊了一個視圖解析器ThymealfViewResolver,並且配置了模板(HTML)的位置,與JSP類似的前綴+視圖名+后綴的風格。我們可以進到它的配置文件(ThymeleafProperties)里去看:
可以發現,如果我們沒有在yml
中進行配置,則它去找的視圖文件默認是在resources
配置文件夾下的templates
文件夾里,后綴為.html
。我們也可以在配置文件中進行配置,即圖中所標注的spring.thymeleaf
下。
yml配置文件
Controller層返回頁面示例
Thymeleaf在Html頁面中的基本
使用
1.要在html頁面中使用thymeleaf的標簽,必須引入名稱空間。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 1
2.表達式的使用
在html頁面中,要使用thymealf的標簽,只需在原標簽名前加th:
即可,如th:src
、th:href
、th:text
等
①${}
,變量表達式,它可以獲取到Controller層存入Model的值
// 后端Controller層代碼
model.addAttribute("name","柳成蔭");
User user = new User("柳成蔭",22); // 姓名、年齡
model.addAttribute("user",user);
- 1
- 2
- 3
- 4
// html頁面中獲取並顯示
<span th:text="${name}">九月</span>
<span th:text="${user.age}">18</span>
- 1
- 2
- 3
前面說過,當有數據傳遞過來時,則動態數據會替換靜態數據。
②*{}
,選擇變量表達式,可以省略對象名,直接獲取屬性值
<div th:object="${user}">
<p th:text="*{name}">九月</p>
<p th:text="*P{age}">18</p>
</div>
- 1
- 2
- 3
- 4
③#{}
,Message表達式,它主要是從國際化配置文件中取值,這里暫不做示例,文章后面將會示例從國際化配置文件中取值。
3、URL的使用
①絕對網址,絕對URL用於創建到其他服務器的鏈接,需要指定協議名稱http
或者https
,如:
<a th:href="@{https://www.baidu.com}">百度</a>
- 1
②上下文相關URL,即與項目根相關聯的URL,這里假設我們的war包為app.war,且沒有在tomcat的server.xml
配置項目的路徑(Context),則在Tomcat啟動之后,就會在webapps文件下產生一個app文件夾,此時app
就是上下文路徑(Context)
<!-- 在頁面這樣寫 -->
<a th:href="@{/blog/search}">跳轉</a>
<!-- Thymeleaf解析之后是這樣的 -->
<a href="/app/blog/search">跳轉</a>
- 1
- 2
- 3
- 4
③服務器相關URL,它與上下文路徑很相似,它主要用於一個Tomcat下運行有多個項目的情況。比如說我們當前項目為app
,如果tomcat還運行着一個otherApp
,我們就可以通過該方法訪問otherApp
的請求路徑。
<!-- 在頁面這樣寫 -->
<a th:href="@{~/otherApp/blog/search}">跳轉</a>
<!-- Thymeleaf解析之后是這樣的 -->
<a href="/otherApp/blog/search">跳轉</a>
- 1
- 2
- 3
- 4
④有時候我們需要頁面帶參數傳遞到后端,則可以使用下面這個方法
<!-- 在頁面這樣寫 -->
<a th:href="@{/blog/search(id=3,blogName='Java')}" >跳轉</a>
<!-- Thymeleaf解析之后是這樣的,這里忽略項目路徑 -->
<a href="/blog/search?id=3&blogName=Java" >跳轉</a>
<!-- 還可以這樣寫,實現restful風格的效果 -->
<a th:href="/blog/{id}/search(id=3&blogName=Java)">跳轉</a>
<!-- Thymeleaf解析之后是這樣的,這里忽略項目路徑 -->
<a href="/blog/3/search?blogName=java">跳轉</a>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4、字面值
有時候,我們需要在指令中填入基本類型如:字符串、數值、布爾等,不希望Thymeleaf去給我們解析,可以這樣做:
①字符串字面值
<!-- 在頁面中這樣寫 -->
<span th:text="‘Thymealf’ + 3">templates</span>
<!-- Thymeleaf解析后 -->
<span>Thymealf3</span>
- 1
- 2
- 3
- 4
②數字字面值
<!-- 在頁面中這樣寫 -->
<span th:text="1 + 3">templates</span>
<!-- Thymeleaf解析后 -->
<span>4</span>
- 1
- 2
- 3
- 4
③布爾字面值:為true和false
5、拼接
傳統拼接需要用''
來進行普通字符串和表達式的拼接,Thymeleaf中進行了簡化,只需將拼接的內容塊使用||
包裹即可:
<span th:text="|歡迎您,${user.name}|">九月</span>
- 1
6、運算符
①因為html里會將<
和>
符號進行解析,所以不能直接使用,但是如果在{}
內使用,是不需要轉換的
> gt 即greater than,大於
< lt 即less than,小於
>= ge 即greater equal,大於等於
<= le 即less equal,小於等於
- 1
- 2
- 3
- 4
②三元運算符
<span th:text="${false} ? '男' : '女'">性別</span>
<>
- 1
- 2
③空值判斷
<!-- 如果user.name為空,則顯示 空值顯示 這幾個字 -->
<span th:text="${user.name} ?: ‘空值顯示’"></span>
- 1
- 2
7、內聯寫法
①[()]
,解析輸出,會解析內容里的html
標簽
<span>[(${user.name})]</span>
- 1
②[[]]
,原樣輸出,不會解析內容里的html
標簽
<span>[[${user.name}]]</span>
- 1
8、局部變量
可以將后端傳來的數據賦值給一個局部變量
,這個局部變量
只能在標簽內部
使用,外部是不可以的
<div th:with="user=${userList[0]}">
<span th:text="${user.name}">昵稱</span>
</div>
- 1
- 2
- 3
9、判斷
①th:if
,滿足條件才顯示標簽包裹的(含標簽)的內容
<span th:if="${true}">顯示</span>
<span th:if="${user.age == 18}">18歲顯示</span>
- 1
- 2
②th:unless
,與th:if
相反,不滿足條件時顯示
<span th:unless='${true}'>不顯示</span>
- 1
③th:switch
,switch的效果一致
<div th:witch="${user.name}">
<span th:case="柳成蔭">柳成蔭</span>
<span th:case="九月">九月</span>
<span th:case="尋寶">尋寶</span>
</div>
- 1
- 2
- 3
- 4
- 5
10、迭代
th:each
,迭代一個集合/數組,可以使用它的內置對象stat
獲取更多的信息。
先列出內置對象stat
的用法:
index:角標,從0開始
count:元素的個數,從1開始,當前遍歷到第幾個
size:元素的總個數
current:當前遍歷到的元素
even/odd:是否為奇/偶,都是返回true或false的布爾結果
first/last:是否第一/最后,都是返回true或false的布爾結果
- 1
- 2
- 3
- 4
- 5
- 6
使用th:each
<tr th:each="user:${userList}">
<td th:text="${user.name}"></td>
<td th:text="|當前迭代到第${stat.count}個了|"></td>
</tr>
- 1
- 2
- 3
- 4
11、環境相關對象
1、${#ctx}
:上下文對象,可用於獲取其他內置對象
2、${#vars}
:上下文變量。
3、${#locale}
:上下文區域設置。
4、${#request}
:HttpServletRequest對象。
5、${#response}
:HttpServletResponse對象。
6、${#session}
:HttpSession對象。
7、${#servletContext}
:ServletContext對象。
具體怎么使用,可以自行百度,這里以session
為例:
<span th:text="${session.user.name}">存儲在session的User對象</span>
- 1
12、全局對象功能
1、#strings
:字符串工具類
2、#lists
:List 工具類
3、#arrays
:數組工具類
4、#sets
:Set 工具類
5、#maps
:常用Map方法。
6、#objects
:一般對象類,通常用來判斷非空
7、#bools
:常用的布爾方法。
8、#execInfo
:獲取頁面模板的處理信息。
9、#messages
:在變量表達式中獲取外部消息的方法,與使用#{…}語法獲取的方法相同。
10、#uris
:轉義部分URL / URI的方法。
11、#conversions
:用於執行已配置的轉換服務的方法。
12、#dates
:時間操作和時間格式化等。
13、#calendars
:用於更復雜時間的格式化。
14、#numbers
:格式化數字對象的方法。
15、#aggregates
:在數組或集合上創建聚合的方法。
16、#ids
:處理可能重復的id屬性的方法。
具體怎么使用,可以自行百度,這里僅以#dates
為例,格式化時間:
<span th:text="${#dates.format(blog.updateTime)},'yyyy-MM-dd HH:mm:ss'"></span>
- 1
13、class屬性增加
使用th:classappend
,可以增加class
元素類名。通常用在如導航欄上,當導航欄上某個標簽被選中,它會與其他沒有被選中的有不同的樣式。
<a class="item m-mobile-hide’ th:classappend='${n==1} ? ‘active’">首頁</a>
<!-- 如果n==1成立,則解析如下 -->
<a class="item m-mobile-hide active’">首頁</a>
- 1
- 2
- 3
Thymeleaf在Html頁面中的布局
使用
定義片段
和引入片段
是非常好的功能,在一個項目中的各個頁面里,通常他們大部分導航欄和底部是相同的。我們可以創建一個頁面專門用來定義重復使用片段,然后在其他頁面做引入。
1、定義片段
①使用th:fragment
定義通用片段 - 普通片段
<nav th:fragment="header">
<a href="#">首頁</a>
<a href="#">分類</a>
</nav>
- 1
- 2
- 3
- 4
②使用th:fragment
定義通用片段 - 帶參片段
如下代碼,如果我們當前是在首頁,則首頁這個標簽就會多一個樣式,而分類就沒有。我們只需要在引入這塊片段的代碼里傳遞一個參數過來進行判斷即可。
<nav th:fragment="header(n)">
<a class="item’ th:classappend='${n==1} ? ‘active’">首頁</a>
<a class="item’ th:classappend='${n==2} ? ‘active’">分類</a>
</nav>
- 1
- 2
- 3
- 4
③使用id
來定義片段 - 不推薦
<nav id="header">
<a href="#">首頁</a>
<a href="#">分類</a>
</nav>
- 1
- 2
- 3
- 4
2、引入片段 - 下文中的_fragment
為定義片段的html頁面
①將公共的標簽插入
到指定標簽中 - 該方法不可以省略~{}
<div th:insert="~{_fragment::header}">
<!-- 被引入的片段會插入到div標簽內部 -->
</div>
- 1
- 2
- 3
②將公共的標簽替換
指定的標簽 - 該方法可以省略~{}
推薦
使用該引入方式,這樣做,可以保證在沒有網絡(動態數據)的情況下,其他頁面也有內容可以顯示。
<!-- 被引入的片段會替換div標簽的內容(含div) -->
<div th:replace="~{_fragment::header}">
<a href="#">鏈接</a>
</div>
- 1
- 2
- 3
- 4
③將公共的標簽包含
到指定的標簽 - 該方法可以省略~{}
<div th:include="~{_fragment::header}">
<!-- 被引入的片段會被包含到div標簽內部 但是最外層的標簽不會被包含進來,即只包含th:fragment所在標簽內部的內容 -->
</div>
- 1
- 2
- 3
- 4
- 5
- 6
④上訴三種方法,可以用來引入使用id
定義片段的 - 不推薦
<nav th:insert="~{_fragment::#header}">
<!-- 不推薦id定義片段和引入這種片段 -->
</nav>
- 1
- 2
- 3
⑤引入帶參片段 - 重要
<nav th:replace="header(2)">
<a class="item’ th:classappend='${n==1} ? ‘active’">首頁</a>
<a class="item’ th:classappend='${n==2} ? ‘active’">分類</a>
</nav>
- 1
- 2
- 3
- 4
3、th:fragment
更新數據
很多時候,在頁面里我們需要使用Ajax
異步請求數據,我們希望只更新某一塊的數據,其他地方不變。這時,就需要th:fragment
來做了,文章前面有提到return "index :: blogList"
這種方式,它就是配合th:fragment
來做的。
<!-- 其他代碼 -->
<div th:fragment="userInfo">
<span th:text="${user.name}">九月</span>
<span th:text="${user.age}">18</span>
</div>
<!-- 其他代碼 -->
- 1
- 2
- 3
- 4
- 5
- 6
public String userInfo(Model model){
model.addAttribute(user,new User("柳成蔭",22));
return "index :: userInfo";
}
- 1
- 2
- 3
- 4
th:fragment
和th:replace
是用在合適的地方非常好用,常見的css塊、js塊、導航欄、底部區域。
4、塊級標簽th:block
th:block
是thymeleaf提供的塊級標簽,其特殊性在於Thymeleaf模板引擎在處理<th:block>
的時候會刪掉它本身,標簽本身不顯示,而保留其內容。
①常見用法 - HTML部分
<!-- 控制幾個標簽是否一起顯示/不顯示 -->
<th:block th:if="...">
<div id="div1">我和div2一起</div>
<div id="div2">我和div1一起</div>
</th:block>
- 1
- 2
- 3
- 4
- 5
<!-- 循環統計標簽 -->
<table>
<th:block th:each="...">
<tr>...</tr>
<tr>...</tr>
</th:block>
</table>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
②常見用法 - JS部分
有時候js
的引入可能也是其他頁面公有的,我們想定義一個js片段
,然后在其他頁面引入。可以使用<div>
將js片段
包裹起來,然后將這個<div>
定義成一個片段,但這種做法並不好。這時就用到了th:block
了
<!-- JS公用部分 - 定義片段 -->
<th:block th:fragment="script">
<script src="../static/js/jquery-3.3.1.min.js" th:src="@{/js/jquery-3.3.1.min.js}"></script>
<script src="../static/js/semantic.min.js" th:src="@{/js/semantic.min.js}"></script>
</th:block>
- 1
- 2
- 3
- 4
- 5
<th:block th:replace="_fragment::script">
<!-- 這樣就解決了JS共用的問題了 -->
<script src="../static/js/jquery-3.3.1.min.js"></script>
</th:block>
- 1
- 2
- 3
- 4
可能有人會問,這個th:block
會不會影響靜態頁面?實際上,它是不會影響靜態頁面的。如果你實在是擔心,我們可以用可以使用下面這種方法
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
<!-- 這樣就解決了JS共用的問題了 -->
<script src="../static/js/jquery-3.3.1.min.js"></script>
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
- 1
- 2
- 3
- 4
注釋的使用
可以看到,上面我們使用了<!-- -->
的方式將th:block
注釋掉了,但是你也發現,我們在里面使用了/*/ /*/
來包裹了標簽。它是thymeleaf注釋的一種,它在運行時,就不會把上面那些注釋了的代碼看做是注釋,而是當做正常的處理。
/*
是另外一種,它的作用就是thymeleaf運行時,把包裹起來的內容注釋掉。
①/*/
注釋
<!-- 下面這行代碼再靜態頁面中確實是被注釋了,但是動態運行時,則不會被注釋 -->
<!--/*/<th:block th:replace="_fragments :: script">/*/-->
- 1
- 2
②/*
注釋
<!-- 下面的注釋在靜態頁面的確注釋了,但是請注意,它注釋的是/*和*/ 而動態運行時,被/*和*/包裹的都要被注釋掉 這樣做的目的是保證靜態運行時可以看到靜態效果,動態運行時看到動態效果 -->
<div>
<span th:each="blog:${blogList}" th:text="${blog.name}">博客1</span>
<!-- /* -->
<span th:each="blog:${blogList}">博客2</span>
<span th:each="blog:${blogList}">博客3</span>
<span th:each="blog:${blogList}">博客4</span>
<!-- */ -->
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
JS模板預處理
模板引擎不僅可以渲染html,也可以對JS中的進行預處理
。而且為了在純靜態環境下可以運行。在script
標簽中通過th:inline="javascript"
來聲明這是要特殊處理的js腳本。
<script th:inline="javascript"> var username = /*[[${user.name}]]*/ ‘默認’; var age = /*[[${user.age}]]*/ ‘18’; console.log(username); console.log(age); </script>
- 1
- 2
- 3
- 4
- 5
- 6
國際化
在SpringBoot項目里面做國際化,只需要在resources/i18n
路徑下創建xxx.properties
、xxx_en_US.properties
、xxx_zh_CN.properties
即可。具體怎么做,將在下文進行演示。
在SpringBoot中有一個messageSourceAutoConfiguration
,它會自動管理國際化資源文件。
也就是說,我們在resources/i18n
創建的國際化配置文件前綴
名為message
時,SpringBoot會自動加載。
當然,我們也可以自己定義國際化文件名稱,這里我們將國際化配置文件命名為login
。
1、去yml
配置文件中配置國際化
spring
messages: i18n.login
- 1
- 2
2、要確保文件編碼是UTF-8
,可以到idea的設置里去設置並讓其自動轉換,Editor
->File Encodings
3、創建i18n
文件夾及3個國際化配置文件,如圖
其中login.properties
是基礎配置文件(默認),如果你的瀏覽器它是其他語言比如法語,是沒有辦法國際化的,所以它就會采用login.properties
在idea里只要創建兩個國際化的配置文件就會自動加入到Resource Bundle 'xxx'
,當然我們還是需要創建三個properties
配置文件。后綴必須按格式寫,_en_US
就是英文,_zh_CN
就是中文。
4、編寫國際化
我們直接點進login.properties,可以看到下方有個切換到Resource Bundle
的按鈕,點擊切換,添加國際化,然后分別在右側寫上對應國際化語言
想添加多少,就可以添加多少。
5、可以在Thymeleaf
頁面進行獲取了,用到前面所說的#{}
<button type="submit" th:text="#{login.btn}"></button>
- 1
6、一般來說我們在頁面會做個切換中英文的按鈕來進行切換
<a th:href="@{/login(lan='zn_CN')}">中文</a>
<a th:href="@{/login(lan='en_US')}">英文</a>
- 1
- 2
7、做完上訴步驟,我們還需要編寫一個本地解析器來進行解析
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 接收語言的參數 - 傳進來的就是形如'zh_CN'這樣的參數
String lan = request.getParameter("lan");
// 使用默認的語言 - 在文中就是login.properties文件里配置的
Locale locale = Locale.getDefault();
// 判斷接收的參數是否為空,不為空就設置為該語言
if(!StringUtils.isEmpty(lan)){
// 將參數分隔 - 假設傳進來的是'zh_CN'
String[] s = lan.split("_");
// 語言編碼:zh 地區編碼:CN
locale = new Locale(s[0],s[1]);
}
return locale;
}
<span class="token annotation punctuation">@Override</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setLocale</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Locale locale<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
8、我們還需要寫一個配置文件來表明國際化解析器
是用的我們自己寫的
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
國際化就這樣完成了,通過上面定義的切換語言按鈕就可以切換了。