Thymeleaf 是 Spring Boot 官方推荐的模板引擎,相比传统的 JSP,它天然支持 HTML 原生语法(无需编译、可直接在浏览器预览),且与 Spring 生态深度整合,是现代 Java Web 开发中动态页面渲染的首选方案。本文将从基础配置、核心语法、高级特性到实战技巧,全方位讲解 Thymeleaf 的使用。
一、基础配置:Spring Boot 整合 Thymeleaf
1.1 依赖引入
在 pom.xml 中添加 Thymeleaf 启动器(Spring Boot 会自动管理版本,2.0.1 版本默认集成 Thymeleaf 3.0.9):
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
1.2 核心自动配置原理
Spring Boot 通过 ThymeleafAutoConfiguration 自动配置 Thymeleaf 核心参数,核心配置类 ThymeleafProperties 定义了默认规则:
| 配置项 | 默认值 | 说明 |
|---|---|---|
prefix |
classpath:/templates/ |
模板文件存放路径 |
suffix |
.html |
模板文件后缀 |
encoding |
UTF-8 | 模板编码 |
cache |
true(生产)/false(开发) | 模板缓存(开发时关闭避免重启) |
mode |
HTML | 模板解析模式(支持 HTML5、XML 等) |
1.3 自定义配置(application.properties)
开发中建议关闭缓存,可自定义模板路径:
XML
# 关闭模板缓存(开发必备)
spring.thymeleaf.cache=false
# 自定义模板前缀(默认 classpath:/templates/)
spring.thymeleaf.prefix=classpath:/templates/views/
# 自定义模板后缀(默认 .html)
spring.thymeleaf.suffix=.html
# 编码格式
spring.thymeleaf.encoding=UTF-8
# 解析模式(HTML5 兼容非严格语法)
spring.thymeleaf.mode=HTML5
1.4 最小化示例
步骤 1:创建模板文件
在 classpath:/templates/ 下新建 success.html,引入 Thymeleaf 命名空间(核心,否则语法不生效):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf 示例</title>
</head>
<body>
<h1 th:text="${welcomeMsg}">默认欢迎语</h1>
</body>
</html>
步骤 2:编写 Controller 传递数据
java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ThymeleafController {
@GetMapping("/success")
public String success(Model model) {
// 向模板传递数据(Model 本质是请求域)
model.addAttribute("welcomeMsg", "Hello Thymeleaf!");
// 返回模板名称(无需后缀,自动拼接 prefix + 名称 + suffix)
return "success";
}
}
启动项目访问 http://localhost:8080/success,页面会渲染出 Hello Thymeleaf!。
二、核心语法:表达式与标签
2.1 标准表达式(核心)
Thymeleaf 表达式是动态渲染的核心,所有表达式都写在 th:* 属性中,支持以下 5 类核心表达式:
| 表达式类型 | 语法 | 作用 |
|---|---|---|
| 变量表达式 | ${...} |
读取 Model/Request/Session/Application 中的数据(Spring EL 语法) |
| 选择表达式 | *{...} |
基于 th:object 选择对象,简化属性访问 |
| 国际化表达式 | #{...} |
读取国际化配置文件(.properties)中的文本 |
| URL 表达式 | @{...} |
生成绝对 / 相对 URL,自动拼接上下文路径(无需手动加项目名) |
| 片段表达式 | ~{...} |
引用模板片段(布局复用) |
2.1.1 变量表达式 ${}
支持读取不同作用域的数据,优先级:Page(请求域)> Request > Session > Application:
html
<!-- 读取 Model 中的数据(请求域) -->
<div th:text="${user.username}">默认用户名</div>
<!-- 读取 Session 中的数据 -->
<div th:text="${session.user.age}">默认年龄</div>
<!-- 读取 Application 中的数据 -->
<div th:text="${application.appName}">默认应用名</div>
<!-- 支持复杂表达式(EL 语法) -->
<div th:text="${user.age > 18 ? '成年' : '未成年'}">默认状态</div>
2.1.2 选择表达式 *{}
通过 th:object 指定根对象,后续用 *{} 简化属性访问(等价于 ${对象.属性}):
html
<div th:object="${user}">
<!-- 等价于 ${user.username} -->
<span th:text="*{username}">用户名</span>
<!-- 等价于 ${user.age} -->
<span th:text="*{age}">年龄</span>
<!-- 支持条件判断 -->
<span th:text="*{gender == 1 ? '男' : '女'}">性别</span>
</div>
2.1.3 国际化表达式 #{}
步骤 1 :创建国际化配置文件(放在 classpath:/i18n/ 下):
messages.properties(默认):welcome=欢迎访问messages_zh_CN.properties(中文):welcome=欢迎访问messages_en_US.properties(英文):welcome=Welcome
步骤 2:配置国际化文件路径:
spring.messages.basename=i18n.messages
spring.messages.encoding=UTF-8
步骤 3:模板中使用:
html
<!-- 基础使用 -->
<h1 th:text="#{welcome}">默认欢迎语</h1>
<!-- 带参数的国际化(配置文件:greet=你好,{0}!) -->
<h2 th:text="#{greet(${user.username})}">默认问候</h2>
2.1.4 URL 表达式 @{}
解决硬编码 URL 的问题,自动拼接上下文路径(如项目名),支持参数:
html
<!-- 相对路径(推荐) -->
<a th:href="@{/user/list}">用户列表</a>
<!-- 带参数的 URL(GET 请求) -->
<a th:href="@{/user/detail(id=${user.id}, name=${user.username})}">用户详情</a>
<!-- 绝对路径 -->
<a th:href="@{http://www.baidu.com}">百度</a>
<!-- 表单提交地址 -->
<form th:action="@{/user/save}" method="post">
<input type="submit" value="提交">
</form>
2.1.5 片段表达式 ~{}
用于复用模板片段(如页眉、页脚),结合 th:replace/th:include/th:insert 使用(下文详解)。
2.2 表达式支持的语法扩展
Thymeleaf 表达式内部支持丰富的运算和逻辑,满足复杂场景:
| 类型 | 语法示例 | ||
|---|---|---|---|
| 字面量 | 文本:'张三'、数字:18、布尔:true/false、空:null |
||
| 文本操作 | 拼接:'姓名:' + ${user.name}、替换:` |
姓名:${user.name} | (更简洁) |
| 算术运算 | +、-、*、/、%(如 ${user.age + 1}) |
||
| 布尔运算 | and/or/not(如 ${user.age > 18 and user.gender == 1}) |
||
| 比较运算 | >、<、>=、<=(gt/lt/ge/le)、==、!=(eq/ne)(如 ${user.score ge 60}) |
||
| 条件运算 | 三元:${user.age > 18 ? '成年' : '未成年'}、默认值:${user.nickname ?: '匿名'} |
示例:
html
<!-- 文本替换(比 + 更简洁) -->
<div th:text="|用户${user.id}:${user.username}|"></div>
<!-- 条件运算 + 默认值 -->
<div th:text="${user.score >= 85 ? '优秀' : (user.score >= 60 ? '及格' : '不及格')}"></div>
<!-- 默认值(变量为空时显示) -->
<div th:text="${user.nickname ?: '未设置昵称'}"></div>
2.3 常用标签(实战高频)
Thymeleaf 通过 th:* 属性覆盖原生 HTML 标签的行为,以下是开发中最常用的标签:
| 标签 | 核心作用 | 实战示例 |
|---|---|---|
th:text |
替换文本(转义 HTML) | <p th:text="${user.desc}">默认描述</p>(<h1> 会被转义为 <h1>) |
th:utext |
替换文本(不转义 HTML) | <p th:utext="${user.htmlDesc}">默认描述</p>(<h1> 会渲染为标题) |
th:each |
循环遍历(集合 / 数组) | <tr th:each="u, stat : ${userList}">(stat 是状态变量) |
th:if |
条件渲染(为 true 时显示) | <div th:if="${user.age > 18}">成年</div> |
th:unless |
条件渲染(为 false 时显示) | <div th:unless="${user.age > 18}">未成年</div> |
th:switch/th:case |
多分支判断 | <div th:switch="${user.role}"><p th:case="admin">管理员</p></div> |
th:value |
设置表单元素 value | <input th:value="${user.username}" /> |
th:selected |
设置下拉框选中状态 | <option th:selected="${u.id eq selectedId}">${u.name}</option> |
th:class |
设置 CSS 类名(支持动态判断) | <div th:class="${user.age > 18 ? 'adult' : 'teen'}"> |
th:src |
设置图片 / 脚本路径(结合 @{}) | <img th:src="@{/img/avatar.png}" /> |
th:onclick |
设置点击事件(支持动态参数) | <button th:onclick="editUser(${user.id})">编辑</button> |
th:fragment |
定义模板片段(复用) | <div th:fragment="header">页眉</div> |
th:replace |
替换标签引入片段(推荐) | <div th:replace="~{common :: header}"></div> |
重点标签详解:
1. th:each 循环(带状态变量)
th:each 遍历集合时,可添加状态变量(stat),包含以下常用属性:
index:索引(从 0 开始)count:计数(从 1 开始)size:集合总大小even/odd:是否偶数 / 奇数行(用于隔行变色)first/last:是否第一个 / 最后一个元素
示例:
html
<table border="1">
<tr>
<th>序号</th>
<th>用户名</th>
<th>密码</th>
</tr>
<tr th:each="u, stat : ${uList}" th:class="${stat.odd ? 'odd-row' : 'even-row'}">
<td th:text="${stat.count}"></td>
<td th:text="${u.username}"></td>
<td th:text="${u.password}"></td>
</tr>
</table>
<style>
.odd-row { background-color: #f5f5f5; }
.even-row { background-color: #ffffff; }
</style>
2. th:replace/th:include/th:insert(布局复用)
三者均用于引入模板片段,核心区别:
th:replace:替换整个标签(推荐,语义最清晰)th:include:引入片段的内容(已废弃)th:insert:将片段插入到当前标签内部
步骤 1 :定义片段(classpath:/templates/common/fragments.html):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<body>
<!-- 定义页眉片段 -->
<div th:fragment="header">
<h1>网站页眉</h1>
<nav>
<a th:href="@{/home}">首页</a> |
<a th:href="@{/user}">用户中心</a>
</nav>
</div>
<!-- 定义页脚片段(带参数) -->
<div th:fragment="footer(copyright)">
<p th:text="${copyright}">默认版权信息</p>
</div>
</body>
</html>
步骤 2:引入片段:
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>布局示例</title>
</head>
<body>
<!-- 引入页眉(无参数) -->
<div th:replace="~{common/fragments :: header}"></div>
<div>页面主体内容</div>
<!-- 引入页脚(传参数) -->
<div th:replace="~{common/fragments :: footer('2026 © 我的网站')}"></div>
</body>
</html>
三、高级特性(实战必备)
3.1 作用域操作
Thymeleaf 支持直接操作 Request/Session/Application 作用域,无需通过 Controller:
html
<!-- 设置 Request 域 -->
<div th:with="requestMsg='请求域消息'"></div>
<!-- 读取 Request 域 -->
<div th:text="${#request.getAttribute('requestMsg')}"></div>
<!-- 设置 Session 域 -->
<div th:with="${session.sessionMsg = '会话域消息'}"></div>
<!-- 读取 Session 域 -->
<div th:text="${session.sessionMsg}"></div>
<!-- 设置 Application 域 -->
<div th:with="${application.appMsg = '应用域消息'}"></div>
<!-- 读取 Application 域 -->
<div th:text="${application.appMsg}"></div>
3.2 工具类(# 前缀)
Thymeleaf 提供了丰富的内置工具类,覆盖字符串、日期、集合等操作,无需在 Controller 处理:
| 工具类 | 作用 | 示例 |
|---|---|---|
#strings |
字符串操作 | #strings.isEmpty(user.name)、#strings.toUpperCase(user.name) |
#dates |
日期格式化 | #dates.format(user.birthday, 'yyyy-MM-dd') |
#numbers |
数字格式化 | #numbers.formatDecimal(user.score, 1, 2)(保留 2 位小数) |
#lists |
集合操作 | #lists.isEmpty(userList)、#lists.size(userList) |
#objects |
对象操作 | #objects.nullSafe(user.nickname, '匿名') |
示例:
html
<!-- 字符串判空 + 转大写 -->
<div th:text="${#strings.isEmpty(user.username) ? '无用户名' : #strings.toUpperCase(user.username)}"></div>
<!-- 日期格式化(避免前端处理时间戳) -->
<div th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}"></div>
<!-- 数字格式化(保留 2 位小数) -->
<div th:text="${#numbers.formatDecimal(user.score, 0, 2)}"></div>
<!-- 集合判空 -->
<div th:if="${#lists.isEmpty(userList)}">暂无数据</div>
3.3 表单处理(数据绑定)
Thymeleaf 支持与 Spring MVC 表单绑定,自动回显数据、显示校验错误:
步骤 1:编写实体类
java
public class UserForm {
private String username;
private String password;
private Integer age;
// getter/setter 省略
}
步骤 2:Controller 传递表单对象
java
@GetMapping("/form")
public String form(Model model) {
// 初始化表单对象(用于回显)
UserForm form = new UserForm();
form.setUsername("默认用户名");
model.addAttribute("userForm", form);
return "form";
}
@PostMapping("/form/save")
public String save(@ModelAttribute UserForm userForm) {
// 处理表单提交
System.out.println(userForm);
return "success";
}
步骤 3:模板中绑定表单
html
<form th:action="@{/form/save}" th:object="${userForm}" method="post">
<!-- 绑定用户名 -->
<div>
用户名:<input type="text" th:field="*{username}" />
</div>
<!-- 绑定密码 -->
<div>
密码:<input type="password" th:field="*{password}" />
</div>
<!-- 绑定年龄 -->
<div>
年龄:<input type="number" th:field="*{age}" />
</div>
<button type="submit">提交</button>
</form>
th:field="*{username}" 等价于 name="username" value="${userForm.username}",自动完成数据绑定和回显。
3.4 异常处理与错误页面
Spring Boot 中可结合 Thymeleaf 实现自定义错误页面:
- 在
classpath:/templates/error/下创建404.html、500.html(Spring Boot 会自动识别); - 模板中可通过
${#request.getAttribute('javax.servlet.error.status_code')}获取错误码。
示例(404.html):
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
<h1>页面找不到啦 😭</h1>
<p>错误码:<span th:text="${#request.getAttribute('javax.servlet.error.status_code')}"></span></p>
<a th:href="@{/home}">返回首页</a>
</body>
</html>
四、实战技巧与避坑指南
4.1 开发调试技巧
- 关闭缓存 :开发时务必设置
spring.thymeleaf.cache=false,否则修改模板后需重启项目; - 静态资源访问 :Thymeleaf 模板中引用 CSS/JS/ 图片,需用
@{/static/css/style.css}(Spring Boot 默认静态资源路径classpath:/static/); - 热部署 :结合
spring-boot-devtools实现模板修改后自动刷新(无需重启)。
4.2 常见坑点
- 命名空间缺失 :忘记加
xmlns:th="http://www.thymeleaf.org",导致表达式不生效; - URL 路径错误 :硬编码 URL 未用
@{},导致项目部署后上下文路径不一致; - 文本转义 :需要渲染 HTML 时用
th:utext,而非th:text(否则 HTML 标签被转义); - 循环状态变量 :
th:each的状态变量是可选的,但需注意顺序(变量名在前,状态变量在后)。
4.3 性能优化
- 生产环境开启缓存 :
spring.thymeleaf.cache=true,减少模板解析开销; - 模板片段复用 :通过
th:fragment复用页眉、页脚等公共部分,减少重复代码; - 避免复杂表达式:复杂逻辑尽量在 Controller 处理,模板只负责渲染。
五、完整实战代码
5.1 实体类(User.java)
java
public class User {
private String username;
private String password;
private Integer age;
private Integer score;
private Integer gender;
private Date birthday;
// getter/setter 省略
}
5.2 Controller(ThymeleafDemoController.java)
java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Controller
public class ThymeleafDemoController {
@GetMapping("/demo")
public String demo(Model model) {
// 单个用户
User user = new User();
user.setUsername("张三");
user.setPassword("123456");
user.setAge(20);
user.setScore(88.5);
user.setGender(1);
user.setBirthday(new Date());
// 用户列表
List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User u = new User();
u.setUsername("用户" + i);
u.setPassword("pwd" + i);
u.setAge(18 + i);
u.setScore(60 + i * 5);
u.setGender(i % 2);
u.setBirthday(new Date());
userList.add(u);
}
model.addAttribute("user", user);
model.addAttribute("userList", userList);
return "demo";
}
}
5.3 模板(demo.html)
html
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf 实战示例</title>
<style>
.odd { background-color: #f5f5f5; }
.even { background-color: #ffffff; }
</style>
</head>
<body>
<h1>单个用户信息</h1>
<div th:object="${user}">
<p>用户名:<span th:text="*{username}"></span></p>
<p>年龄:<span th:text="*{age}"></span>(<span th:text="*{age > 18 ? '成年' : '未成年'}"></span>)</p>
<p>性别:<span th:switch="*{gender}">
<span th:case="1">男</span>
<span th:case="0">女</span>
</span></p>
<p>分数:<span th:text="${#numbers.formatDecimal(user.score, 0, 2)}"></span></p>
<p>生日:<span th:text="${#dates.format(user.birthday, 'yyyy-MM-dd')}"></span></p>
</div>
<hr>
<h1>用户列表</h1>
<table border="1" width="80%">
<tr>
<th>序号</th>
<th>用户名</th>
<th>年龄</th>
<th>分数等级</th>
</tr>
<tr th:each="u, stat : ${userList}" th:class="${stat.odd ? 'odd' : 'even'}">
<td th:text="${stat.count}"></td>
<td th:text="${u.username}"></td>
<td th:text="${u.age}"></td>
<td th:text="${u.score >= 85 ? '优秀' : (u.score >= 60 ? '及格' : '不及格')}"></td>
</tr>
</table>
</body>
</html>
六、总结
- 核心基础 :Thymeleaf 与 Spring Boot 整合只需引入依赖,默认配置即可使用,模板放在
classpath:/templates/下,需引入xmlns:th命名空间; - 核心语法 :掌握
${}(变量)、*{}(选择)、@{}(URL)、~{}(片段)四类表达式,以及th:each/th:if/th:replace高频标签; - 实战技巧 :利用内置工具类(
#strings/#dates)简化前端处理,通过模板片段实现布局复用,生产环境开启缓存优化性能。
Thymeleaf 的核心优势是 "自然模板"(可直接在浏览器打开预览)和 Spring 生态深度整合,掌握以上内容足以应对 90% 以上的 Web 开发场景。