Appearance
Thymeleaf:现代化的服务端模板引擎 🎨
什么是 Thymeleaf?
Thymeleaf 是一个现代化的服务端 Java 模板引擎,它的核心理念是让 HTML 模板保持"自然"状态——即使没有服务器运行,你也可以直接在浏览器中双击打开 HTML 文件进行预览。
NOTE
想象一下,前端设计师可以独立工作,无需启动整个 Spring Boot 应用就能看到页面效果,这就是 Thymeleaf 的魅力所在!
为什么需要 Thymeleaf?🤔
传统 JSP 的痛点
在 Thymeleaf 出现之前,我们主要使用 JSP 来处理服务端渲染:
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<c:forEach var="user" items="${users}">
<div>
<p>姓名: ${user.name}</p>
<p>邮箱: ${user.email}</p>
</div>
</c:forEach>
</body>
</html>
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<div th:each="user : ${users}">
<p>姓名: <span th:text="${user.name}">示例用户</span></p>
<p>邮箱: <span th:text="${user.email}">[email protected]</span></p>
</div>
</body>
</html>
TIP
注意看 Thymeleaf 版本中的 "示例用户" 和 "[email protected]",这些是静态预览时显示的内容,而在服务器渲染时会被实际数据替换。
Thymeleaf 解决的核心问题
Spring Boot 中集成 Thymeleaf
1. 添加依赖
kotlin
// build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.springframework.boot:spring-boot-starter-web")
}
NOTE
Spring Boot 的自动配置会自动处理 Thymeleaf 的核心组件:ServletContextTemplateResolver
、SpringTemplateEngine
和 ThymeleafViewResolver
。
2. 配置文件设置
yaml
# application.yml
spring:
thymeleaf:
prefix: classpath:/templates/ # 模板文件路径
suffix: .html # 模板文件后缀
mode: HTML # 模板模式
encoding: UTF-8 # 字符编码
cache: false # 开发时关闭缓存
WARNING
生产环境中应该将 cache
设置为 true
以提高性能!
3. 控制器示例
kotlin
@Controller
class UserController {
@GetMapping("/users")
fun listUsers(model: Model): String {
// 模拟用户数据
val users = listOf(
User("张三", "[email protected]", 25),
User("李四", "[email protected]", 30),
User("王五", "[email protected]", 28)
)
model.addAttribute("users", users)
model.addAttribute("title", "用户管理系统")
return "user-list" // 返回模板名称,对应 templates/user-list.html
}
@GetMapping("/user/{id}")
fun userDetail(@PathVariable id: Long, model: Model): String {
val user = User("张三", "[email protected]", 25)
model.addAttribute("user", user)
return "user-detail"
}
}
data class User(
val name: String,
val email: String,
val age: Int
)
4. Thymeleaf 模板
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">用户列表</title>
<style>
.user-card {
border: 1px solid #ccc;
margin: 10px;
padding: 15px;
border-radius: 5px;
}
</style>
</head>
<body>
<h1 th:text="${title}">用户管理系统</h1>
<!-- 条件渲染 -->
<div th:if="${#lists.isEmpty(users)}">
<p>暂无用户数据</p>
</div>
<!-- 循环渲染 -->
<div th:unless="${#lists.isEmpty(users)}">
<div class="user-card" th:each="user, iterStat : ${users}">
<h3>用户 #<span th:text="${iterStat.count}">1</span></h3>
<p>姓名: <strong th:text="${user.name}">示例姓名</strong></p>
<p>邮箱: <span th:text="${user.email}">[email protected]</span></p>
<p>年龄: <span th:text="${user.age}">0</span> 岁</p>
<!-- 条件样式 -->
<span th:if="${user.age >= 30}"
th:class="'badge-senior'"
style="color: red;">资深用户</span>
<span th:unless="${user.age >= 30}"
style="color: green;">年轻用户</span>
</div>
</div>
</body>
</html>
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户详情</title>
</head>
<body>
<div th:object="${user}">
<h1>用户详情</h1>
<p>姓名: <span th:text="*{name}">姓名</span></p>
<p>邮箱: <a th:href="'mailto:' + *{email}" th:text="*{email}">邮箱</a></p>
<p>年龄: <span th:text="*{age}">年龄</span></p>
<!-- URL 生成 -->
<a th:href="@{/users}">返回用户列表</a>
</div>
</body>
</html>
Thymeleaf 核心语法速览 📚
常用表达式
表达式类型 | 语法 | 说明 | 示例 |
---|---|---|---|
变量表达式 | ${...} | 获取上下文变量 | ${user.name} |
选择表达式 | *{...} | 在选定对象上执行 | *{name} (需配合 th:object ) |
URL 表达式 | @{...} | 生成 URL | @{/users/{id}(id=${user.id})} |
消息表达式 | #{...} | 国际化消息 | #{welcome.message} |
常用属性
html
<!-- 文本内容 -->
<span th:text="${message}">默认文本</span>
<div th:utext="${htmlContent}">HTML内容</div>
<!-- 属性设置 -->
<input th:value="${user.name}" />
<img th:src="@{/images/logo.png}" />
<a th:href="@{/user/{id}(id=${user.id})}">查看详情</a>
<!-- 条件判断 -->
<div th:if="${user.age >= 18}">成年用户</div>
<div th:unless="${user.age >= 18}">未成年用户</div>
<!-- 循环 -->
<li th:each="item : ${items}" th:text="${item}">项目</li>
<!-- 表单绑定 -->
<form th:object="${user}" th:action="@{/user/save}" method="post">
<input th:field="*{name}" />
<input th:field="*{email}" />
</form>
实际业务场景应用 🚀
场景:电商商品列表页面
kotlin
@Controller
class ProductController {
@GetMapping("/products")
fun listProducts(
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "") category: String,
model: Model
): String {
val products = productService.findProducts(page, category)
val categories = categoryService.findAllCategories()
model.addAttribute("products", products)
model.addAttribute("categories", categories)
model.addAttribute("currentCategory", category)
model.addAttribute("currentPage", page)
return "product-list"
}
}
完整的商品列表模板示例
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>商品列表</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
<h1>商品列表</h1>
<!-- 分类筛选 -->
<div class="mb-3">
<a th:href="@{/products}"
th:class="${#strings.isEmpty(currentCategory)} ? 'btn btn-primary' : 'btn btn-outline-primary'">
全部
</a>
<a th:each="cat : ${categories}"
th:href="@{/products(category=${cat.name})}"
th:text="${cat.name}"
th:class="${currentCategory == cat.name} ? 'btn btn-primary ms-2' : 'btn btn-outline-primary ms-2'">
分类
</a>
</div>
<!-- 商品网格 -->
<div class="row">
<div class="col-md-4 mb-4" th:each="product : ${products}">
<div class="card">
<img th:src="@{/images/products/{img}(img=${product.image})}"
class="card-img-top" alt="商品图片">
<div class="card-body">
<h5 class="card-title" th:text="${product.name}">商品名称</h5>
<p class="card-text" th:text="${product.description}">商品描述</p>
<p class="text-danger fw-bold">
¥<span th:text="${#numbers.formatDecimal(product.price, 0, 2)}">0.00</span>
</p>
<a th:href="@{/product/{id}(id=${product.id})}"
class="btn btn-primary">查看详情</a>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<nav th:if="${totalPages > 1}">
<ul class="pagination justify-content-center">
<li th:class="${currentPage == 0} ? 'page-item disabled' : 'page-item'">
<a class="page-link"
th:href="@{/products(page=${currentPage - 1}, category=${currentCategory})}">
上一页
</a>
</li>
<li th:each="i : ${#numbers.sequence(0, totalPages - 1)}"
th:class="${i == currentPage} ? 'page-item active' : 'page-item'">
<a class="page-link"
th:href="@{/products(page=${i}, category=${currentCategory})}"
th:text="${i + 1}">1</a>
</li>
<li th:class="${currentPage == totalPages - 1} ? 'page-item disabled' : 'page-item'">
<a class="page-link"
th:href="@{/products(page=${currentPage + 1}, category=${currentCategory})}">
下一页
</a>
</li>
</ul>
</nav>
</div>
</body>
</html>
Thymeleaf vs 其他模板引擎 ⚖️
特性 | Thymeleaf | JSP | FreeMarker |
---|---|---|---|
自然模板 | ✅ 支持 | ❌ 不支持 | ❌ 不支持 |
静态预览 | ✅ 可以 | ❌ 不可以 | ❌ 不可以 |
Spring 集成 | ✅ 原生支持 | ✅ 传统支持 | ✅ 良好支持 |
学习曲线 | 📈 适中 | 📈 较陡 | 📈 较陡 |
性能 | 🚀 良好 | 🚀 优秀 | 🚀 优秀 |
最佳实践建议 💡
开发建议
- 开发环境关闭缓存:
spring.thymeleaf.cache=false
- 使用片段复用:创建通用的 header、footer 模板片段
- 合理使用表达式:优先使用
*{...}
配合th:object
简化代码 - 静态资源路径:使用
@{...}
确保路径正确
性能优化
- 生产环境启用模板缓存
- 避免在模板中进行复杂的业务逻辑处理
- 合理使用条件渲染,避免不必要的 DOM 元素
常见陷阱
- 忘记添加 Thymeleaf 命名空间声明
- 在循环中使用复杂表达式影响性能
- 混用不同类型的表达式语法
总结 🎯
Thymeleaf 作为现代化的模板引擎,完美解决了传统 JSP 开发中前后端协作困难的问题。通过其"自然模板"的设计理念,让 HTML 模板既能独立预览,又能与 Spring Boot 无缝集成,大大提升了开发效率和团队协作体验。
对于 Spring Boot 开发者来说,Thymeleaf 不仅是 JSP 的优秀替代品,更是构建现代 Web 应用的强大工具! 🎉