1. 为什么选择Thymeleaf?
在Java Web开发中,模板引擎的作用是将业务数据动态渲染到页面上。传统的JSP虽然功能强大,但存在编译速度慢、与Servlet容器强耦合等问题。而Thymeleaf作为Spring Boot官方推荐的模板引擎,具有以下优势:
- 自然模板:Thymeleaf模板本身就是标准的HTML文件,无需特殊工具即可直接在浏览器中预览
- 无侵入性:通过HTML属性(如
th:text)实现数据绑定,不会破坏原始HTML结构 - 开箱即用:Spring Boot提供了自动配置,只需添加依赖即可快速集成
- 功能全面:支持表达式、迭代、条件判断、模板布局等常见功能
我曾在电商后台项目中用Thymeleaf替换JSP,页面加载速度提升了40%,开发效率也显著提高。特别是在前后端协作时,前端可以直接修改静态HTML,后端只需关注数据绑定逻辑。
2. 环境搭建与基础配置
2.1 创建Spring Boot项目
使用Spring Initializr创建项目时,勾选以下依赖:
- Spring Web
- Thymeleaf
或者直接在pom.xml中添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>2.2 基础配置
在application.properties中添加常用配置:
# 模板文件存放路径(默认值) spring.thymeleaf.prefix=classpath:/templates/ # 文件后缀(默认值) spring.thymeleaf.suffix=.html # 关闭缓存(开发时建议关闭) spring.thymeleaf.cache=false # 模板模式 spring.thymeleaf.mode=HTML # 编码格式 spring.thymeleaf.encoding=UTF-8提示:生产环境记得开启缓存(
spring.thymeleaf.cache=true)以提高性能
3. 第一个Thymeleaf页面
3.1 创建控制器
@Controller public class HelloController { @GetMapping("/hello") public String sayHello(Model model) { model.addAttribute("message", "欢迎使用Thymeleaf"); model.addAttribute("currentTime", LocalDateTime.now()); return "hello"; // 对应templates/hello.html } }3.2 创建模板文件
在resources/templates下创建hello.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="${#dates.format(currentTime, 'yyyy-MM-dd HH:mm')}"></span> </p> <!-- 条件判断 --> <div th:if="${currentTime.hour > 12}"> 现在是下午时间 </div> <div th:unless="${currentTime.hour > 12}"> 现在是上午时间 </div> </body> </html>启动应用后访问http://localhost:8080/hello,你将看到动态渲染的页面。这里有几个关键点:
xmlns:th命名空间声明,这是使用Thymeleaf语法的前提th:text用于文本替换,静态时显示默认文本,运行时替换为动态值- 使用
#dates工具对象进行日期格式化
4. 核心语法详解
4.1 表达式类型
Thymeleaf支持多种表达式:
| 表达式 | 示例 | 说明 |
|---|---|---|
| ${...} | ${user.name} | 变量表达式 |
| *{...} | *{name} | 选择表达式(需配合th:object使用) |
| #{...} | #{login.button} | 国际化消息 |
| @{...} | @{/css/style.css} | URL表达式 |
| ~{...} | ~{commons::header} | 片段引用 |
变量表达式示例:
<p>用户名:<span th:text="${user.username}"></span></p>选择表达式示例:
<div th:object="${user}"> <p>姓名:<span th:text="*{name}"></span></p> <p>年龄:<span th:text="*{age}"></span></p> </div>4.2 常用属性
| 属性 | 说明 |
|---|---|
| th:text | 文本替换 |
| th:utext | 非转义文本(支持HTML) |
| th:value | 表单元素值 |
| th:each | 循环迭代 |
| th:if | 条件判断 |
| th:href | 链接地址 |
| th:src | 资源路径 |
循环列表示例:
<table> <tr th:each="user : ${users}"> <td th:text="${user.id}"></td> <td th:text="${user.name}"></td> <td th:text="${user.age}"></td> </tr> </table>带状态的循环:
<ul> <li th:each="user,iter : ${users}" th:text="|${iter.index+1}. ${user.name}|"> </li> </ul>iter对象包含以下属性:
- index:当前迭代索引(从0开始)
- count:当前迭代计数(从1开始)
- size:集合大小
- current:当前元素
- even/odd:奇偶判断
- first/last:首尾项判断
4.3 表单处理
Thymeleaf与Spring MVC的表单绑定非常方便:
<form th:action="@{/user/save}" th:object="${user}" method="post"> <input type="hidden" th:field="*{id}"> <label>用户名:</label> <input type="text" th:field="*{username}"> <label>密码:</label> <input type="password" th:field="*{password}"> <button type="submit">保存</button> </form>th:field会自动绑定name和value属性,实现数据回显。我在实际项目中遇到过字段名拼写错误导致绑定失败的情况,建议使用IDE的代码提示功能避免这类问题。
5. 高级特性
5.1 模板布局
通过片段复用实现页面公共部分抽取:
定义片段(commons.html):
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head th:fragment="commonHead(title)"> <meta charset="UTF-8"> <title th:text="${title}">默认标题</title> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> </head> <body> <header th:fragment="header"> <nav>导航栏内容</nav> </header> <footer th:fragment="footer"> <p>版权信息 © 2023</p> </footer> </body> </html>使用片段:
<html> <head th:replace="commons :: commonHead('用户管理')"></head> <body> <div th:replace="commons :: header"></div> <main> <!-- 页面主要内容 --> </main> <div th:replace="commons :: footer"></div> </body> </html>5.2 内联表达式
在JavaScript中使用Thymeleaf数据:
<script th:inline="javascript"> var user = [[${user}]]; console.log('用户名:' + user.username); /*<![CDATA[*/ function showAlert() { alert([[#{login.welcome}]]); } /*]]>*/ </script>注意:使用
/*<![CDATA[*/ ... /*]]>*/包裹代码避免XML解析问题
5.3 工具对象
Thymeleaf提供了丰富的工具对象:
| 工具类 | 功能 |
|---|---|
| #dates | 日期格式化、计算 |
| #strings | 字符串操作 |
| #numbers | 数字格式化 |
| #lists | 集合操作 |
| #arrays | 数组操作 |
| #objects | 对象工具 |
| #bools | 布尔工具 |
日期格式化示例:
<p th:text="${#dates.format(user.createTime, 'yyyy年MM月dd日')}"></p>字符串判空示例:
<span th:if="${#strings.isEmpty(user.remark)}">暂无备注</span>6. 实战:用户管理系统
下面我们实现一个完整的用户管理功能,包含列表展示、添加、编辑和删除。
6.1 实体类
@Data // Lombok注解 public class User { private Long id; private String username; private String password; private Integer age; private LocalDateTime createTime; }6.2 控制器
@Controller @RequestMapping("/user") public class UserController { // 模拟数据库 private List<User> users = new ArrayList<>(); private AtomicLong idGenerator = new AtomicLong(1); @GetMapping public String list(Model model) { model.addAttribute("users", users); return "user/list"; } @GetMapping("/add") public String addForm(Model model) { model.addAttribute("user", new User()); return "user/form"; } @PostMapping("/save") public String save(@ModelAttribute User user) { if(user.getId() == null) { user.setId(idGenerator.getAndIncrement()); user.setCreateTime(LocalDateTime.now()); users.add(user); } else { // 更新逻辑 } return "redirect:/user"; } @GetMapping("/delete/{id}") public String delete(@PathVariable Long id) { users.removeIf(u -> u.getId().equals(id)); return "redirect:/user"; } }6.3 列表页面(list.html)
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>用户列表</title> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h2>用户管理</h2> <a th:href="@{/user/add}" class="btn btn-primary mb-3">新增用户</a> <table class="table table-striped"> <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}"></td> <td th:text="${user.username}"></td> <td th:text="${user.age}"></td> <td th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm')}"></td> <td> <a th:href="@{'/user/delete/' + ${user.id}}" class="btn btn-danger btn-sm" onclick="return confirm('确定删除吗?')">删除</a> </td> </tr> <tr th:if="${#lists.isEmpty(users)}"> <td colspan="5" class="text-center">暂无数据</td> </tr> </tbody> </table> </div> </body> </html>6.4 表单页面(form.html)
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title th:text="${user.id} != null ? '编辑用户' : '新增用户'"></title> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h2 th:text="${user.id} != null ? '编辑用户' : '新增用户'"></h2> <form th:action="@{/user/save}" th:object="${user}" method="post"> <input type="hidden" th:field="*{id}"> <div class="form-group"> <label>用户名</label> <input type="text" class="form-control" th:field="*{username}"> </div> <div class="form-group"> <label>密码</label> <input type="password" class="form-control" th:field="*{password}"> </div> <div class="form-group"> <label>年龄</label> <input type="number" class="form-control" th:field="*{age}"> </div> <button type="submit" class="btn btn-primary">保存</button> </form> </div> </body> </html>7. 常见问题与优化建议
7.1 开发调试技巧
热加载:在开发时关闭缓存并添加devtools依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency>IDEA插件:安装Thymeleaf插件避免表达式报红
错误排查:遇到模板解析错误时,检查:
- HTML标签是否闭合
- 属性值是否使用正确引号
- 表达式语法是否正确
7.2 性能优化
启用缓存:生产环境务必设置
spring.thymeleaf.cache=true合理使用片段:避免过度拆分模板文件
静态资源处理:正确配置资源映射
spring.mvc.static-path-pattern=/static/** spring.web.resources.static-locations=classpath:/static/
7.3 安全建议
防XSS:默认
th:text会进行HTML转义,非必要不使用th:utextCSRF防护:表单提交添加CSRF token
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">权限控制:结合Spring Security实现按钮级权限
<button th:if="${#authorization.expression('hasRole(''ADMIN'')')}"> 管理员操作 </button>
8. 扩展应用
8.1 邮件模板
Thymeleaf非常适合生成HTML邮件内容:
@Autowired private TemplateEngine templateEngine; public void sendWelcomeEmail(User user) { Context ctx = new Context(); ctx.setVariable("user", user); String htmlContent = templateEngine.process("email/welcome", ctx); // 发送邮件逻辑... }8.2 PDF导出
配合Flying Saucer库实现HTML转PDF:
@GetMapping("/export") public void exportPdf(HttpServletResponse response) throws Exception { Context ctx = new Context(); ctx.setVariable("users", userService.findAll()); String html = templateEngine.process("user/pdf", ctx); response.setContentType("application/pdf"); ITextRenderer renderer = new ITextRenderer(); renderer.setDocumentFromString(html); renderer.layout(); renderer.createPDF(response.getOutputStream()); }在实际项目中,我使用这种方案实现了报表导出功能,相比传统POI方式代码量减少了60%。
9. 最佳实践总结
经过多个项目的实践验证,以下Thymeleaf使用经验值得分享:
目录结构规范:
resources/ ├── static/ # 静态资源 │ ├── css/ │ ├── js/ │ └── images/ └── templates/ # 模板文件 ├── commons/ # 公共片段 ├── modules/ # 业务模块 └── layouts/ # 布局文件命名约定:
- 模板文件:小写+连字符,如
user-list.html - 片段文件:名词复数,如
commons.html
- 模板文件:小写+连字符,如
代码组织:
- 避免在模板中编写复杂逻辑
- 公共片段保持单一职责
- 使用注释标明区块用途
团队协作:
- 制定模板编写规范
- 建立公共片段库
- 定期进行代码评审
Thymeleaf的学习曲线平缓,但真正掌握需要实践积累。建议从简单页面开始,逐步尝试更复杂的应用场景。当遇到问题时,官方文档和社区都是很好的资源。