一、Thymeleaf基础概念
1 什么是Thymeleaf
Thymeleaf是一个现代化的服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML、XML、JavaScript、CSS甚至纯文本。
核心特点:
- 自然模板:Thymeleaf模板可以作为静态原型直接在浏览器中打开
- Spring集成:与Spring框架无缝集成,特别是Spring MVC
- 丰富的表达式语言:支持Spring EL表达式
- 模块化:支持模板布局和片段重用
- 国际化支持:内置国际化支持
2. Thymeleaf vs 其他模板引擎
特性 | Thymeleaf | JSP | Freemarker | Velocity |
---|---|---|---|---|
自然模板 | ✔️ | ❌ | ❌ | ❌ |
Spring集成 | ✔️ | ✔️ | ✔️ | ✔️ |
学习曲线 | 中等 | 低 | 低 | 低 |
性能 | 中等 | 高 | 高 | 中 |
HTML5支持 | ✔️ | ❌ | ❌ | ❌ |
静态原型 | ✔️ | ❌ | ❌ | ❌ |
3. Thymeleaf核心概念
模板(Template):包含静态内容和动态占位符的文件
表达式(Expression):用于访问和操作模型数据的语法
处理器(Processor):处理特定Thymeleaf属性的组件
方言(Dialect):一组处理器和表达式的集合
二、Spring Boot集成Thymeleaf
1. 添加依赖
在pom.xml
中添加Thymeleaf starter依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 基本配置
Spring Boot为Thymeleaf提供了自动配置,默认配置如下:
配置项 | 默认值 | 说明 |
---|---|---|
spring.thymeleaf.prefix | classpath:/templates/ | 模板文件位置 |
spring.thymeleaf.suffix | .html | 模板文件后缀 |
spring.thymeleaf.mode | HTML | 模板模式 |
spring.thymeleaf.cache | true(生产)/false(开发) | 是否缓存模板 |
可以在application.properties
中自定义配置:
# 开发时关闭缓存,修改模板后立即生效
spring.thymeleaf.cache=false
# 自定义模板位置
spring.thymeleaf.prefix=classpath:/views/
# 设置编码
spring.thymeleaf.encoding=UTF-8
# 设置内容类型
spring.thymeleaf.servlet.content-type=text/html
3. 创建第一个Thymeleaf页面
控制器代码:
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "欢迎来到Thymeleaf世界!");
model.addAttribute("currentDate", new Date());
return "home"; // 对应templates/home.html
}
}
模板文件src/main/resources/templates/home.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1 th:text="${message}">默认标题</h1>
<p>当前时间: <span th:text="${currentDate}">2023-01-01</span></p>
<!-- 这是一个Thymeleaf注释,不会出现在渲染后的HTML中 -->
<div th:if="${not #strings.isEmpty(message)}">
<p th:text="'消息长度: ' + ${#strings.length(message)}"></p>
</div>
</body>
</html>
4. 静态资源处理
Spring Boot默认静态资源位置:
classpath:/static/
classpath:/public/
classpath:/resources/
在Thymeleaf中引用静态资源:
<!-- 引用CSS -->
<link th:href="@{/css/style.css}" rel="stylesheet">
<!-- 引用JS -->
<script th:src="@{/js/app.js}"></script>
<!-- 引用图片 -->
<img th:src="@{/images/logo.png}" alt="Logo">
三、Thymeleaf基本语法
1. 标准表达式语法
表达式类型 | 语法示例 | 说明 |
---|---|---|
变量表达式 | ${user.name} | 访问模型属性 |
选择表达式 | *{name} | 在选定对象上执行 |
消息表达式 | #{home.welcome} | 国际化消息 |
链接表达式 | @{/user/list} | URL构建 |
片段表达式 | ~{footer :: copy} | 模板片段引用 |
2. 常用Thymeleaf属性
属性 | 说明 | 示例 |
---|---|---|
th:text | 设置文本内容 | <p th:text="${message}">默认文本</p> |
th:utext | 设置文本内容(不转义HTML) | <div th:utext="${htmlContent}"></div> |
th:value | 设置表单元素值 | <input th:value="${user.name}"> |
th:each | 循环 | <tr th:each="user : ${users}"> |
th:if | 条件判断 | <div th:if="${user.active}"> |
th:unless | 否定条件判断 | <div th:unless="${user.admin}"> |
th:switch | 多条件选择 | <div th:switch="${user.role}"> |
th:href | 设置链接URL | <a th:href="@{/user/{id}(id=${user.id})}"> |
th:src | 设置资源URL | <img th:src="@{/images/logo.png}"> |
th:class | 动态设置class | <div th:class="${isActive} ? 'active' : 'inactive'"> |
3. 表达式实用示例
变量表达式:
<p>用户名: <span th:text="${user.username}">默认用户名</span></p>
<p>年龄: <span th:text="${user.age}">0</span></p>
条件判断:
<div th:if="${user.age >= 18}">
<p>您已成年</p>
</div>
<div th:unless="${user.age >= 18}">
<p>您未成年</p>
</div>
循环:
<table>
<tr th:each="product, iterStat : ${products}">
<td th:text="${iterStat.index + 1}">1</td>
<td th:text="${product.name}">产品名</td>
<td th:text="${product.price}">价格</td>
<td th:text="${iterStat.odd} ? '奇数行' : '偶数行'">行状态</td>
</tr>
</table>
URL链接:
<!-- 简单URL -->
<a th:href="@{/products}">产品列表</a>
<!-- 带路径变量的URL -->
<a th:href="@{/products/{id}(id=${product.id})}">产品详情</a>
<!-- 带查询参数的URL -->
<a th:href="@{/search(keyword=${keyword},page=1)}">搜索</a>
4. 实用工具对象
Thymeleaf提供了一系列实用工具对象:
工具对象 | 说明 | 示例 |
---|---|---|
#dates | 日期格式化 | ${#dates.format(date, 'yyyy-MM-dd')} |
#calendars | 日历操作 | ${#calendars.day(date)} |
#numbers | 数字格式化 | ${#numbers.formatDecimal(price, 1, 2)} |
#strings | 字符串操作 | ${#strings.isEmpty(name)} |
#objects | 对象操作 | ${#objects.nullSafe(obj, default)} |
#bools | 布尔操作 | ${#bools.isTrue(flag)} |
#arrays | 数组操作 | ${#arrays.length(array)} |
#lists | 列表操作 | ${#lists.contains(list, element)} |
#sets | 集合操作 | ${#sets.size(set)} |
#maps | Map操作 | ${#maps.containsKey(map, key)} |
使用示例:
<p th:text="${#dates.format(user.birthday, 'yyyy年MM月dd日')}">1990-01-01</p>
<p th:text="${#strings.toUpperCase(user.name)}">张三</p>
<p th:text="${#numbers.formatDecimal(product.price, 1, 2)}">99.99</p>
四、Thymeleaf与表单处理
1. 表单绑定基础
控制器:
@Controller
public class UserController {
@GetMapping("/user/form")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "user-form";
}
@PostMapping("/user/save")
public String saveUser(@ModelAttribute User user, Model model) {
model.addAttribute("user", user);
return "user-detail";
}
}
表单模板user-form.html
:
<form th:action="@{/user/save}" th:object="${user}" method="post">
<div>
<label>用户名:</label>
<input type="text" th:field="*{username}">
<span th:if="${#fields.hasErrors('username')}"
th:errors="*{username}">用户名错误</span>
</div>
<div>
<label>邮箱:</label>
<input type="email" th:field="*{email}">
<span th:if="${#fields.hasErrors('email')}"
th:errors="*{email}">邮箱错误</span>
</div>
<div>
<label>年龄:</label>
<input type="number" th:field="*{age}">
</div>
<button type="submit">提交</button>
</form>
2. 表单验证
实体类:
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 2, max = 20, message = "用户名长度必须在2-20之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须大于18岁")
private int age;
// getters and setters
}
控制器修改:
@PostMapping("/user/save")
public String saveUser(@Valid @ModelAttribute User user, BindingResult result) {
if (result.hasErrors()) {
return "user-form";
}
return "user-detail";
}
3. 复选框和单选按钮
表单模板:
<div>
<label>性别:</label>
<input type="radio" th:field="*{gender}" value="MALE"> 男
<input type="radio" th:field="*{gender}" value="FEMALE"> 女
</div>
<div>
<label>兴趣爱好:</label>
<input type="checkbox" th:field="*{hobbies}" value="SPORTS"> 运动
<input type="checkbox" th:field="*{hobbies}" value="MUSIC"> 音乐
<input type="checkbox" th:field="*{hobbies}" value="READING"> 阅读
</div>
4. 下拉选择框
控制器:
@GetMapping("/user/form")
public String showForm(Model model) {
model.addAttribute("user", new User());
Map<String, String> countries = new LinkedHashMap<>();
countries.put("CN", "中国");
countries.put("US", "美国");
countries.put("JP", "日本");
model.addAttribute("countries", countries);
return "user-form";
}
表单模板:
<div>
<label>国家:</label>
<select th:field="*{country}">
<option value="">-- 请选择国家 --</option>
<option th:each="entry : ${countries}"
th:value="${entry.key}"
th:text="${entry.value}">国家</option>
</select>
</div>
五、Thymeleaf高级特性
1. 内联表达式
内联表达式允许在HTML文本中直接使用Thymeleaf表达式,而不用th:text属性。
<p>Hello, [[${user.name}]]!</p>
<p>Hello, [(${user.name})]!</p>
区别:
[[...]]
会进行HTML转义[(...)]
不会进行HTML转义
可以在JavaScript中使用内联表达式:
<script th:inline="javascript">
var user = {
name: /*[[${user.name}]]*/ '默认名',
age: /*[[${user.age}]]*/ 0
};
</script>
2. 模板布局
定义基础模板layout.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${title}">默认标题</title>
<meta charset="UTF-8">
<link th:href="@{/css/layout.css}" rel="stylesheet">
</head>
<body>
<header>
<h1>网站标题</h1>
<nav th:replace="~{fragments/nav :: main-nav}">默认导航</nav>
</header>
<div class="content" th:fragment="content">
<!-- 内容将被子模板替换 -->
<p>默认内容</p>
</div>
<footer th:replace="~{fragments/footer :: main-footer}">
默认页脚
</footer>
</body>
</html>
子模板继承:
<html th:replace="~{layout :: layout(~{::title}, ~{::content})}">
<head>
<title>用户管理</title>
</head>
<body>
<div th:fragment="content">
<h2>用户列表</h2>
<table>
<!-- 用户表格内容 -->
</table>
</div>
</body>
</html>
3. 片段表达式
定义片段fragments/nav.html
:
<nav th:fragment="main-nav">
<ul>
<li><a th:href="@{/}">首页</a></li>
<li><a th:href="@{/products}">产品</a></li>
<li><a th:href="@{/about}">关于</a></li>
</ul>
</nav>
使用片段:
<div th:replace="~{fragments/nav :: main-nav}"></div>
<!-- 传递参数给片段 -->
<div th:replace="~{fragments/nav :: main-nav(activeTab='products')}"></div>
4. 局部变量
<div th:with="firstUser=${users[0]}">
<p th:text="${firstUser.name}">用户名</p>
</div>
5. 模板缓存控制
在开发阶段,建议关闭缓存以便实时查看修改:
spring.thymeleaf.cache=false
在生产环境,应该开启缓存提高性能:
spring.thymeleaf.cache=true
六、Thymeleaf与国际化
1. 配置国际化
application.properties:
spring.messages.basename=messages
spring.messages.encoding=UTF-8
创建消息文件:
messages.properties
(默认)messages_zh_CN.properties
(中文)messages_en_US.properties
(英文)
中文消息文件内容:
home.welcome=欢迎来到我们的网站!
user.name=用户名
user.email=电子邮箱
2. 在模板中使用国际化
<h1 th:text="#{home.welcome}">Welcome</h1>
<label th:text="#{user.name}">Username</label>
<input type="text" name="username">
<!-- 带参数的消息 -->
<p th:text="#{welcome.message(${user.name})}">Hello, User!</p>
3. 语言切换
控制器:
@Controller
public class LocaleController {
@GetMapping("/changeLang")
public String changeLocale(@RequestParam String lang,
HttpServletRequest request,
HttpServletResponse response) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
localeResolver.setLocale(request, response, new Locale(lang));
return "redirect:" + request.getHeader("Referer");
}
}
语言切换链接:
<a th:href="@{/changeLang(lang='zh_CN')}">中文</a>
<a th:href="@{/changeLang(lang='en_US')}">English</a>
七、Thymeleaf性能优化
1. 缓存策略
策略 | 说明 | 适用场景 |
---|---|---|
全缓存 | 所有模板都缓存 | 生产环境 |
无缓存 | 不缓存任何模板 | 开发环境 |
部分缓存 | 只缓存特定模板 | 特殊需求 |
2. 模板优化技巧
- 减少模板复杂度:避免过度嵌套和复杂逻辑
- 使用片段重用:将重复部分提取为片段
- 合理使用布局:使用布局减少重复代码
- 避免大循环:大数据集考虑分页
- 减少内联脚本:将JavaScript移到外部文件
3. 监控模板渲染时间
在application.properties
中启用监控:
logging.level.org.thymeleaf=DEBUG
八、常见问题与解决方案
1. 模板解析错误
问题:模板语法错误导致无法解析
解决:检查错误信息,通常Thymeleaf会给出详细的行号和错误原因
2. 静态资源404
问题:CSS/JS/图片无法加载
解决:
- 确保资源放在
static
或public
目录 - 使用正确的引用方式:
th:href="@{/path/to/resource}"
- 检查Spring Security配置是否拦截了静态资源
3. 表达式不生效
问题:${...}
或*{...}
表达式没有渲染
解决:
- 确保模型中有对应属性
- 检查属性名拼写
- 确保HTML有
xmlns:th="http://www.thymeleaf.org"
声明
4. 表单绑定失败
问题:表单提交后模型属性没有绑定
解决:
- 确保表单有
th:object
属性 - 确保输入字段有
th:field
属性 - 检查控制器方法参数是否有
@ModelAttribute
九、最佳实践
1. 项目结构建议
src/main/resources/
├── static/ # 静态资源
│ ├── css/
│ ├── js/
│ └── images/
├── templates/ # 模板文件
│ ├── fragments/ # 片段
│ ├── layout.html # 基础布局
│ ├── home.html # 首页
│ └── user/ # 用户相关模板
└── messages.properties # 国际化文件
2. 命名约定
- 模板文件:小写字母,短横线分隔,如
user-profile.html
- 片段:放在
fragments
目录,如fragments/nav.html
- 变量名:与Java命名一致,驼峰式
3. 安全考虑
- 始终对用户输入进行转义,除非明确需要HTML内容
- 避免在模板中直接使用用户输入构建JavaScript
- 对敏感操作使用CSRF保护
4. 测试建议
- 编写单元测试验证控制器返回正确的视图名
- 使用MockMvc测试完整的请求-响应流程
- 验证模板在不同语言环境下的渲染
十、完整示例:用户管理系统
1. 控制器
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list";
}
@GetMapping("/add")
public String showAddForm(Model model) {
model.addAttribute("user", new User());
model.addAttribute("roles", Role.values());
return "user/form";
}
@PostMapping("/save")
public String saveUser(@Valid @ModelAttribute User user,
BindingResult result,
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "user/form";
}
userService.save(user);
redirectAttributes.addFlashAttribute("message", "用户保存成功");
return "redirect:/users";
}
@GetMapping("/edit/{id}")
public String showEditForm(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
model.addAttribute("roles", Role.values());
return "user/form";
}
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable Long id, RedirectAttributes redirectAttributes) {
userService.delete(id);
redirectAttributes.addFlashAttribute("message", "用户删除成功");
return "redirect:/users";
}
}
2. 列表模板user/list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layout :: layout(~{::title}, ~{::content})}">
<head>
<title>用户列表</title>
</head>
<body>
<div th:fragment="content">
<h2>用户列表</h2>
<div th:if="${message}" class="alert alert-success" th:text="${message}"></div>
<a th:href="@{/users/add}" class="btn btn-primary">添加用户</a>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>角色</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1</td>
<td th:text="${user.username}">admin</td>
<td th:text="${user.email}">admin@example.com</td>
<td th:text="${user.role}">ADMIN</td>
<td>
<a th:href="@{/users/edit/{id}(id=${user.id})}" class="btn btn-sm btn-info">编辑</a>
<a th:href="@{/users/delete/{id}(id=${user.id})}"
class="btn btn-sm btn-danger"
onclick="return confirm('确定删除吗?')">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
3. 表单模板user/form.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layout :: layout(~{::title}, ~{::content})}">
<head>
<title th:text="${user.id} ? '编辑用户' : '添加用户'">用户表单</title>
</head>
<body>
<div th:fragment="content">
<h2 th:text="${user.id} ? '编辑用户' : '添加用户'">用户表单</h2>
<form th:action="@{/users/save}" th:object="${user}" method="post">
<input type="hidden" th:field="*{id}">
<div class="form-group">
<label th:text="#{user.username}">用户名</label>
<input type="text" th:field="*{username}" class="form-control">
<span th:if="${#fields.hasErrors('username')}"
th:errors="*{username}" class="text-danger"></span>
</div>
<div class="form-group">
<label th:text="#{user.email}">邮箱</label>
<input type="email" th:field="*{email}" class="form-control">
<span th:if="${#fields.hasErrors('email')}"
th:errors="*{email}" class="text-danger"></span>
</div>
<div class="form-group">
<label th:text="#{user.role}">角色</label>
<select th:field="*{role}" class="form-control">
<option th:each="role : ${roles}"
th:value="${role}"
th:text="${role}">角色</option>
</select>
</div>
<button type="submit" class="btn btn-primary">保存</button>
<a th:href="@{/users}" class="btn btn-secondary">取消</a>
</form>
</div>
</body>
</html>
总结
本文全面介绍了Spring Boot整合Thymeleaf的各个方面,从基础配置到高级特性,包括:
- 基础集成:如何将Thymeleaf添加到Spring Boot项目
- 模板语法:各种表达式和属性的使用方法
- 表单处理:表单绑定、验证和各类表单元素的使用
- 高级特性:布局、片段、内联表达式等
- 国际化:多语言支持实现
- 性能优化:缓存策略和模板优化技巧
- 最佳实践:项目结构、命名约定和安全考虑
Thymeleaf作为Spring生态中的首选模板引擎,提供了强大的功能和良好的开发体验。通过本文的学习,你应该能够:
- 熟练使用Thymeleaf开发各种Web页面
- 实现复杂的页面布局和组件复用
- 处理表单和用户输入验证
- 构建国际化的Web应用
- 优化模板性能
头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。