一、什么是 Thymeleaf?
Thymeleaf 是一个现代化的服务器端 Java 模板引擎,主要用于 Web 和独立环境。
主要特点:
-
自然模板:可直接在浏览器中打开查看静态效果
-
与 Spring 完美集成:Spring 官方推荐模板引擎
-
功能强大:支持各种表达式和标签
-
可扩展:支持自定义方言
二、基础语法
1. 引入命名空间
html
<html xmlns:th="http://www.thymeleaf.org">
2. 常用属性前缀
| 前缀 | 作用 | 示例 |
|---|---|---|
th:text |
文本替换 | <p th:text="${message}"></p> |
th:utext |
不转义HTML | <p th:utext="${htmlContent}"></p> |
th:value |
设置值 | <input th:value="${user.name}"> |
th:href |
链接 | <a th:href="@{/user/list}">用户列表</a> |
th:src |
资源路径 | <img th:src="@{/images/logo.png}"> |
th:each |
循环 | <tr th:each="user : ${users}"> |
th:if |
条件判断 | <div th:if="${user.active}">激活</div> |
th:unless |
否定条件 | <div th:unless="${user.active}">未激活</div> |
th:switch |
多条件选择 | <div th:switch="${user.role}"> |
th:case |
条件分支 | <p th:case="'admin'">管理员</p> |
三、表达式语法
1. 变量表达式 ${}
html
<p th:text="${user.name}">默认文本</p>
<p th:text="${user?.address?.city}">安全导航</p>
2. 选择表达式 *{}
html
<div th:object="${user}">
<p th:text="*{name}"></p>
<p th:text="*{age}"></p>
</div>
3. 链接表达式 @{}
html
<!-- 绝对路径 -->
<a th:href="@{http://www.example.com}">外部链接</a>
<!-- 相对路径 -->
<a th:href="@{/user/list}">用户列表</a>
<!-- 带参数 -->
<a th:href="@{/user/detail(id=${user.id})}">用户详情</a>
<!-- 路径变量 -->
<a th:href="@{/user/{id}/detail(id=${user.id})}">详情</a>
<!-- 多个参数 -->
<a th:href="@{/user/edit(id=${user.id},type=${type})}">编辑</a>
4. 消息表达式 #{} (国际化)
html
<p th:text="#{welcome.message}">Welcome</p>
<p th:text="#{user.name(${username})}">Hello, John</p>
5. 片段表达式 ~{}
html
<!-- 引入公共片段 -->
<div th:replace="~{fragments/header :: header}"></div>
<!-- 只引入片段的一部分 -->
<div th:replace="~{commons :: sidebar}"></div>
四、常用功能示例
1. 循环遍历
html
<table>
<tr th:each="user, stat : ${users}">
<td th:text="${stat.index}">序号</td>
<td th:text="${user.name}">姓名</td>
<td th:text="${user.email}">邮箱</td>
<td>
<span th:text="${stat.odd}? '奇数行' : '偶数行'"></span>
<span th:text="${stat.first}? '第一行' : ''"></span>
</td>
</tr>
</table>
<!-- 循环状态变量 -->
<!-- stat.index:0开始的索引 -->
<!-- stat.count:1开始的计数 -->
<!-- stat.size:总数量 -->
<!-- stat.current:当前对象 -->
<!-- stat.even/odd:是否偶数/奇数 -->
<!-- stat.first/last:是否第一/最后 -->
2. 条件判断
html
<!-- if/unless -->
<div th:if="${user.active}">
用户已激活
</div>
<div th:unless="${user.active}">
用户未激活
</div>
<!-- switch/case -->
<div th:switch="${user.role}">
<p th:case="'admin'">管理员</p>
<p th:case="'user'">普通用户</p>
<p th:case="*">其他角色</p>
</div>
3. 表单处理
html
<form th:action="@{/user/save}" th:object="${user}" method="post">
<!-- 隐藏字段 -->
<input type="hidden" th:field="*{id}">
<!-- 文本输入 -->
<input type="text" th:field="*{name}"
class="form-control"
th:classappend="${#fields.hasErrors('name')} ? 'is-invalid'">
<div th:if="${#fields.hasErrors('name')}"
th:errors="*{name}" class="invalid-feedback"></div>
<!-- 单选按钮 -->
<input type="radio" th:field="*{gender}" value="M"> 男
<input type="radio" th:field="*{gender}" value="F"> 女
<!-- 复选框 -->
<input type="checkbox" th:field="*{agree}"> 同意协议
<!-- 下拉选择 -->
<select th:field="*{city}">
<option value="">请选择</option>
<option th:each="city : ${cities}"
th:value="${city.code}"
th:text="${city.name}">
</option>
</select>
<!-- 日期选择 -->
<input type="date" th:field="*{birthday}">
<button type="submit">保存</button>
</form>
4. 日期格式化
html
<!-- 使用 #temporals 工具 -->
<p th:text="${#temporals.format(user.createTime, 'yyyy-MM-dd HH:mm')}"></p>
<p th:text="${#temporals.day(user.birthday)}"></p>
<p th:text="${#temporals.month(user.birthday)}"></p>
<p th:text="${#temporals.year(user.birthday)}"></p>
<!-- 在属性中使用 -->
<input type="date" th:value="${#temporals.format(user.birthday, 'yyyy-MM-dd')}">
5. 字符串操作
html
<p th:text="${#strings.toUpperCase(user.name)}"></p>
<p th:text="${#strings.toLowerCase(user.name)}"></p>
<p th:text="${#strings.substring(user.name, 0, 3)}"></p>
<p th:text="${#strings.length(user.name)}"></p>
<p th:text="${#strings.replace(user.name, 'a', 'b')}"></p>
<p th:text="${#strings.isEmpty(user.name)}"></p>
<p th:text="${#strings.contains(user.name, 'admin')}"></p>
6. 集合操作
html
<p th:text="${#lists.size(userList)}"></p>
<p th:text="${#lists.isEmpty(userList)}"></p>
<p th:text="${#lists.contains(userList, targetUser)}"></p>
<!-- 集合工具 -->
<p th:if="${#lists.contains(user.roles, 'ADMIN')}">有管理员权限</p>
五、布局和片段
1. 定义片段
fragments/header.html:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title, css)">
<meta charset="UTF-8">
<title th:text="${title}">默认标题</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
<th:block th:replace="${css}"></th:block>
</head>
<body>
<header th:fragment="header">
<nav>
<a th:href="@{/}">首页</a>
<a th:href="@{/user/list}">用户管理</a>
</nav>
</header>
<footer th:fragment="footer">
<p>© 2024 公司名称</p>
</footer>
</body>
</html>
2. 使用片段
page.html:
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="~{layouts/base :: layout(~{::head}, ~{::body})}">
<head th:replace="~{fragments/header :: head('用户列表', ~{::link})}">
<link rel="stylesheet" th:href="@{/css/user.css}">
</head>
<body>
<div th:replace="~{fragments/header :: header}"></div>
<main>
<h1>用户列表</h1>
<!-- 页面内容 -->
</main>
<div th:replace="~{fragments/header :: footer}"></div>
</body>
</html>
六、实用工具对象
| 工具 | 用途 | 示例 |
|---|---|---|
#ctx |
上下文信息 | ${#ctx.locale} |
#vars |
所有变量 | ${#vars.containsKey('user')} |
#locale |
本地化信息 | ${#locale.language} |
#request |
HttpServletRequest | ${#request.getAttribute('foo')} |
#response |
HttpServletResponse | ${#response.getContentType()} |
#session |
HttpSession | ${#session.getAttribute('user')} |
#servletContext |
ServletContext | ${#servletContext.contextPath} |
#dates |
日期工具(已废弃) | 使用 #temporals |
#calendars |
日历工具 | ${#calendars.createNow()} |
#numbers |
数字格式化 | ${#numbers.formatDecimal(price, 1, 2)} |
#strings |
字符串工具 | ${#strings.toUpperCase(name)} |
#objects |
对象工具 | ${#objects.nullSafe(obj, 'default')} |
#bools |
布尔工具 | ${#bools.isTrue(value)} |
#arrays |
数组工具 | ${#arrays.length(array)} |
#lists |
List工具 | ${#lists.size(list)} |
#sets |
Set工具 | ${#sets.size(set)} |
#maps |
Map工具 | ${#maps.size(map)} |
#aggregates |
聚合工具 | ${#aggregates.sum(array)} |
#messages |
国际化消息 | ${#messages.msg('key')} |
#ids |
ID生成 | ${#ids.seq('user')} |
#conversions |
转换服务 | ${#conversions.convert(obj, String)} |
七、最佳实践
1. 避免在HTML中写太多逻辑
html
<!-- 不推荐 -->
<div th:if="${user.active and (user.role == 'ADMIN' or user.role == 'MANAGER')}">
高级用户
</div>
<!-- 推荐:在控制器中处理复杂逻辑 -->
<div th:if="${isAdvancedUser}">
高级用户
</div>
2. 使用片段提高复用性
html
<!-- 创建通用的表单字段片段 -->
<div th:fragment="input-field(field, label, type='text')">
<label th:text="${label}"></label>
<input th:type="${type}"
th:field="*{${field}}"
th:classappend="${#fields.hasErrors(field)} ? 'is-invalid'">
<div th:if="${#fields.hasErrors(field)}"
th:errors="*{${field}}"
class="invalid-feedback"></div>
</div>
<!-- 使用 -->
<div th:replace="~{fragments/form :: input-field('username', '用户名')}"></div>
<div th:replace="~{fragments/form :: input-field('password', '密码', 'password')}"></div>
3. 安全性考虑
html
<!-- 转义HTML防止XSS -->
<p th:text="${userInput}"></p> <!-- 自动转义 -->
<p th:utext="${trustedHtml}"></p> <!-- 不转义,谨慎使用 -->
<!-- 在URL中编码参数 -->
<a th:href="@{/search(keyword=${userInput})}">搜索</a>
八、常见问题
1. 表达式不生效
-
检查是否引入命名空间
-
检查是否使用正确的表达式语法
-
检查数据是否确实传入模型
2. 链接错误
html
<!-- 错误 -->
<a th:href="/user/list"></a>
<!-- 正确 -->
<a th:href="@{/user/list}"></a>
3. 静态资源无法加载
html
<!-- 使用 @{} 表达式 -->
<script th:src="@{/js/main.js}"></script>
<link th:href="@{/css/style.css}" rel="stylesheet">
Thymeleaf 功能非常丰富,以上是最常用的部分。实际开发中可以根据需求查阅官方文档获取更多高级功能。