Thymeleaf 是一個 Java 伺服器端的 HTML 模板引擎,能直接在 HTML 文件嵌入模板語法進行撰寫,讓開發更加直覺。 可通過 spring-boot-starter-thymeleaf 整合到 SpringBoot 專案,是 JVM Web 開發的理想選擇。
讓我們通過簡單的案例快速了解如何使用吧 !
在 pom.xml
中加入依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
建立配置類別
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.dialect.SpringStandardDialect;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
@Configuration
@EnableWebMvc
@ComponentScan
public class ThymeleafConfig implements WebMvcConfigurer, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public ITemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
// 模板資源路徑的前綴
templateResolver.setPrefix("classpath:/templates/");
// 模板資源路徑的後綴
templateResolver.setSuffix(".html");
// 編碼
templateResolver.setCharacterEncoding("utf-8");
// 快取設定
templateResolver.setCacheable(false);
// 模板模式
templateResolver.setTemplateMode("html5");
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setEnableSpringELCompiler(true);
Set<IDialect> sets = new HashSet<IDialect> ();
sets.add(new LayoutDialect());
sets.add(new Java8TimeDialect());
sets.add( new SpringStandardDialect());
templateEngine.setDialects(sets);
return templateEngine;
}
@Bean
public ViewResolver viewResolver(SpringTemplateEngine engine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(engine);
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setRedirectHttp10Compatible(false);
viewResolver.setCache(false);
viewResolver.setOrder(0);
return viewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 靜態資源的路徑
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
}
在 src/main/resources/templates
資料夾下新增模板檔案 hello.html
。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello Page</title>
</head>
<body>
<h1>Hello, <span th:text="${userName}"></span>!</h1>
</body>
</html>
可以看出這不是標準的 HTML 格式,裡面包含了 Thymeleaf 的語法。
為了讓 IDE 可以正確解讀,必須在 <html>
加上 xmlns:th="http://www.thymeleaf.org"
。
建立 Controller 端點
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MyController {
@GetMapping("/hello")
public String hello(Model model) {
// Model 是模板資料的容器,用 (key, value) 形式儲存
// 其內的值會被賦予到 Thymeleaf 執行環境
model.addAttribute("userName", "Joe");
// 前面已經配置模板資源路徑的規則
// return 會指到 src/main/resources/templates/hello.html
return "hello";
}
}
在 hello.html
中,有一段是這樣的:
<h1>Hello, <span th:text="${userName}"></span>!</h1>
th:text="${userName}"
表示從 Thymeleaf 執行環境中取變數 userName
的值,以純文字型態取代 HTML 元素的內容。
經過 Thymeleaf 處理後會輸出成:
<h1>Hello, <span>Joe</span>!</h1>
當你從瀏覽器進入 "/hello"
端點,看到的應該是 Hello, Joe!
的文字。
Thymeleaf 的語法主要是在 HTML 標籤中嵌入由 th:
開頭的屬性表示使用的 Thymeleaf 功能,並用表達式 (Expression)、字面量 (Literal) 與運算子 (Operator) 對屬性賦值。
如前述範例中的:
<h1>Hello, <span th:text="${userName}"></span>!</h1>
Variable Expressions: ${...}
- 從 Thymeleaf 執行環境取變數,範例:
// 用變數名稱取值
${title}
// 變數是 Object 時,可用 (.) 接變數名或 ([]) 包圍變數名取值
// 等於呼叫 Object 的 Getter
${person.father.name}
${person['father']['name']}
// 變數是 Map 時,(.) 接變數名或用 ([]) 包圍變數名取值
// 等於於呼叫 Map 的 get(...)
${countriesByCode.ES}
${personsByName['Stephen Zucchini'].age}
// 變數是 Array 或 Collection,(.) 接 index 或用 ([]) 包圍 index 取值
${personsArray[0].name}
// 可以呼叫物件的方法
${person.createCompleteName()}
${person.createCompleteNameWithSeparator('-')}
Selection Variable Expressions: *{...}
- 從選擇的物件中取變數,範例:
// 用 th:object 選擇物件
<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}">
// 用 ${...} 表達式,並用 #object 取得已選擇的物件來取變數
<p>Name: <span th:text="${#object.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>
Link URL Expressions: @{...}
- 用於 URL 連結變數
支援以下幾種路徑:
http://www.thymeleaf.org
/itemdetails?id=3
(環境名稱會自動加上)~/billing/processInvoice
(用於呼叫同一伺服器上的另一隻程式)//code.jquery.com/jquery-2.0.3.min.js
範例:
<!-- 連結會被重寫為 'http://localhost:8080/gtvg/order/details?orderId=3' -->
<a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a>
<!-- 連結會被重寫為 '/gtvg/order/details?orderId=3' -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- 連結會被重寫為 '/gtvg/order/3/details' -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
'one text'
, 'Another one!'
,…0
, 34
, 3.0
, 12.3
,…true
, false
null
one
, sometext
, main
,…數字、布林值、空值其實都算一種 Literal tokens。它用起來跟文字字面量類似,但省略
('...')
且只能用(A-Z)
、(a-z)
、(0-9)
、括號[
與]
、點(.)
、連字號(-)
、底線(_)
。所以
<div th:class="'content'">...</div>
可以改寫成
<div th:class="content">...</div>
+
|The name is ${name}|
+
, -
, *
, /
, %
and
, or
, !
, not
>
, <
, >=
, <=
(gt
, lt
, ge
, le
)==
, !=
(eq
, ne
)(if) ? (then)
(if) ? (then) : (else)
(value) ?: (defaultvalue)
_
無動作運算子
_
表示不要做任何動作,例如:<span th:text="${user.name} ?: _">no user authenticated</span>
使用條件運算子 Default,當
${user.name}
為null
時不做動作,直接使用<span>
內預先設定的內容。
有兩種方法:
我們可以使用 th:attr
賦值給任何屬性,如範例:
<form action="subscribe.html" th:attr="action=@{/subscribe}">
</form>
th:attr
設定的 action
值會取代原本設定的 subscribe.html
,結果會被輸出成:
<form action="/gtvg/subscribe">
</form>
th:attr
可讀性不好,所以通常會使用其它專門設定特定屬性的 th:*
標籤,如 th:action
。
<form action="subscribe.html" th:action="@{/subscribe}">
</form>
Thymeleaf 支援大部分 HTML5 屬性的賦值標籤,這裡不全部列出,請參考連結: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
即便屬性不在支援列表裡也不用擔心,Thymeleaf 預設對沒定義在標準語法裡的屬性一樣可以用
th:*
的方式賦值。
HTML 中有種特殊的布林屬性,這種屬性沒有值,它本身就代表 true
。
例如 checked
:
<input type="checkbox" name="active" checked />
Thymeleaf 支援這類布林屬性的 th:*
標籤,當設定給標籤的值是 true
時設定屬性,是 false
時則不設定屬性。
<input type="checkbox" name="active" th:checked="${user.active}" />
Thymeleaf 支援的布林屬性這裡不全部列出,請參考連結: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#fixed-value-boolean-attributes
th:each
用於迴圈遍歷物件內的變數,我們用迴圈列出 List
內的資料到 <table>
做範例:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? 'yes' : 'no'">yes</td>
</tr>
</table>
th:each="prod : ${prods}"
表示迴圈遍歷 ${prods}
內的變數,用 prod
代表當下存取到的變數,重複用此模板片段進行處理。
${prods}
稱為 受迭代變數 (iterated variable) 。prod
稱為 迭代變數 (iter variable) 。若要取得迴圈的狀態資料,有兩種方法:
使用 Thymeleaf 自動產生的 狀態變數 (status variable) ,變數名為 迭代變數 (iter variable) 名稱加上 Stat
後綴:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? 'yes' : 'no'">yes</td>
</tr>
</table>
在迭代變數後面新增一個變數作為 狀態變數 (status variable) :
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? 'yes' : 'no'">yes</td>
</tr>
</table>
狀態變數 (status variable) 內有以下資料:
even
/ odd
布林變數確認。first
布林變數確認。last
布林變數確認。th:each
可用於以下物件:
java.util.Iterable
的物件java.util.Enumeration
的物件java.util.Iterator
的物件java.util.Map
的物件th:if
可讓 HTML 片段只在符合條件時出現,例如:
<a th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
th:if="${not #lists.isEmpty(prod.comments)}"
表示只有符合 " prod.comments
不為空 list
" 的條件時才出現這個 HTML 片段。
th:if
除了接受布林值外也可用一些特殊的值做為輸入,當符合以下規則時也視為 true
:
true
。0
。'0'
。“false”
, “off”
或 “no”
。th:if
還有一個相反的標籤 th:unless
,表示只有在條件不符合時才讓片段出現。
前面的範例可以用 th:unless
改寫成:
<a th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
th:switch
/ th:case
可用於根據條件顯示不同內容,範例:
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="'manager'">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
th:case="*"
表示預設值。