在微服务和前后端分离大行其道的今天,服务端模板渲染依然有它不可替代的场景。内部管理系统、小型企业官网、原型验证项目,这类项目对交互要求不高,用一套 Thymeleaf 模板就能把页面和数据一起搞定,省去了前端工程化、接口联调、跨域配置等一系列麻烦。Spring Boot 对 Thymeleaf 的支持开箱即用,整合成本极低,本文就完整走一遍从零搭建到页面渲染的流程。

引入依赖与基础配置
整合的第一步是在 pom.xml 中加入 Thymeleaf 的 starter 依赖。Spring Boot 会自动完成大部分装配工作,开发者几乎不需要写任何配置类。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>这个 starter 内部已经包含了 spring-boot-starter-web,所以不需要重复引入 web 依赖。依赖加进来之后,Thymeleaf 的默认约定是到 classpath:/templates/ 目录下寻找模板文件,到 classpath:/static/ 目录下寻找静态资源,这个约定优先于配置,只有确实需要调整时才在 application.yml 中显式声明。
虽然默认约定已经够用,但了解几个常用配置项仍然有价值,比如开发期关闭缓存让页面修改即时生效,生产环境再打开:
spring:
thymeleaf:
cache: false # 开发环境关闭模板缓存
encoding: UTF-8 # 模板编码
mode: HTML # 模板模式
prefix: classpath:/templates/
suffix: .html这里最容易踩的坑是 cache: false 忘记改回 true 就上了生产环境。模板缓存关闭意味着每次请求都要重新解析模板,高并发下会带来明显的性能损耗。建议通过多环境配置(dev 和 prod 分开)来管理这个开关,避免手滑。
编写控制器与模板页面
配置完成后,接下来看控制器和模板如何配合。控制器负责准备数据并指定视图名,Thymeleaf 负责把数据填充进模板渲染出最终 HTML。先写一个简单的 Controller:
@Controller
public class UserController {
@GetMapping("/users")
public String listUsers(Model model) {
List<User> users = new ArrayList<>();
users.add(new User(1L, "张三", "zhangsan@ipipp.com"));
users.add(new User(2L, "李四", "lisi@ipipp.com"));
model.addAttribute("userList", users);
model.addAttribute("title", "用户列表");
return "user/list"; // 对应 templates/user/list.html
}
}注意这里的 @Controller 不能误写成 @RestController,后者会把返回值直接当作响应体输出,浏览器拿到的就是一段字符串而不是渲染后的页面。返回的视图名 user/list 会拼接上配置中的前缀和后缀,最终定位到 templates/user/list.html 文件。
模板页面这边,需要在 html 标签上声明 Thymeleaf 的命名空间,这样 IDE 才能提供语法提示:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="${title}">默认标题</title>
</head>
<body>
<h1 th:text="${title}">用户管理</h1>
<table border="1">
<tr>
<th>ID</th><th>姓名</th><th>邮箱</th>
</tr>
<tr th:each="user : ${userList}">
<td th:text="${user.id}">1</td>
<td th:text="${user.name}">张三</td>
<td th:text="${user.email}">test@ipipp.com</td>
</tr>
</table>
</body>
</html>这个例子体现了 Thymeleaf 最讨人喜欢的特性:原型可用。标签里写的静态内容(比如“张三”)在没有数据时也能正常显示,设计师拿到的 HTML 可以直接在浏览器里打开预览,加上 th:* 属性后就变成了动态模板,前后协作时沟通成本大大降低。
常用语法:变量、条件与内联表达式
除了 th:text 和 th:each,日常开发中还有几组高频语法必须掌握。th:if 和 th:unless 控制元素是否渲染,th:switch 处理多分支,th:object 配合 *{} 表达式可以简化对象属性的访问。
<div th:object="${user}">
<p>姓名:<span th:text="*{name}">--</span></p>
<p th:if="*{vip}">会员标识:VIP 用户</p>
<p th:unless="*{vip}">普通用户</p>
</div>
<!-- 内联写法,直接在文本中使用表达式 -->
<p>欢迎你,[[${user.name}]]</p>内联表达式 [[...]] 在拼接少量动态文本时非常方便,避免了为了一个变量单独加 span 标签。需要注意 th:if 的判断逻辑与 Java 略有差异:非零数字、非空字符串、字符串 "false" 之外的值都会被判定为真,null 和零判定为假,写条件时要多留个心眼。
日期和数字的格式化也有现成工具,比如 th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm')}" 可以直接格式化日期,不必在控制器里先转成字符串,保持模板表达式的简洁。
表单提交与数据回显
管理系统的核心场景之一就是表单。Thymeleaf 提供了 th:action、th:field 等属性来简化表单处理,其中 th:field 会自动生成 name 和 id 属性,并绑定了对应的数据回显。
<form th:action="@{/user/save}" th:object="${user}" method="post">
<input type="text" th:field="*{name}" placeholder="请输入姓名"/>
<input type="text" th:field="*{email}" placeholder="请输入邮箱"/>
<button type="submit">提交</button>
</form>对应的控制器需要提供 GET 和 POST 两个入口,GET 用于打开表单页(携带一个空对象用于回显),POST 用于接收提交:
@Controller
@RequestMapping("/user")
public class UserFormController {
@GetMapping("/add")
public String addPage(Model model) {
model.addAttribute("user", new User());
return "user/form";
}
@PostMapping("/save")
public String save(@ModelAttribute User user) {
// 保存逻辑省略
return "redirect:/users"; // 提交成功后重定向到列表页
}
}提交成功后返回 redirect: 前缀的视图名是经典的最佳实践,重定向可以防止用户刷新页面时重复提交表单。而如果校验失败,直接返回原视图名,th:field 会自动把用户刚填的数据回显到表单里,用户体验比手写回显逻辑省心得多。
如果要提示错误信息,可以配合 Spring 的 BindingResult,在模板中用 th:errors 或 th:if="${#fields.hasErrors('name')}" 来逐字段展示校验失败原因,形成完整的表单闭环。
静态资源处理与常见问题排查
CSS、JS、图片等静态资源默认放在 src/main/resources/static 下,模板中引用时强烈建议使用 @{...} 链接表达式。如果项目配置了 context-path,或者后续部署到 Nginx 子路径下,写死的路径会全部失效,而 @{} 会自动带上应用上下文前缀:
<link rel="stylesheet" th:href="@{/css/style.css}"/>
<script th:src="@{/js/app.js}"></script>
<a th:href="@{/user/add}">新增用户</a>开发过程中还有几个高频报错值得提前了解。一是 TemplateInputException,提示找不到模板,多半是视图名和文件路径对不上,或者文件后缀写成了 .htm、.tpl 之类的非默认后缀。二是页面输出乱码,检查 application.yml 中的 encoding 配置以及模板文件本身的保存编码是否为 UTF-8。三是修改模板不生效,九成是缓存没关,配置 cache: false 后重启即可;也可以额外引入 spring-boot-devtools 实现包括模板在内的资源热加载,进一步提升开发效率。
整体来看,Spring Boot 加 Thymeleaf 的组合胜在简单直接:数据流转清晰、SEO 友好、无需维护两套工程。只要页面交互不是特别复杂,这套前后端不分离的方案依然能打,掌握本文这些语法和排查手段,就足够支撑一个中型管理系统的页面开发需求了。
Spring BootThymeleaf前后端不分离修改时间:2026-09-08 11:13:05