别再死记硬背了!用这5个Thymeleaf实战小项目,彻底搞懂SpringBoot模板引擎
5个Thymeleaf实战项目带你玩转SpringBoot模板引擎
如果你已经啃完了Thymeleaf的语法手册,却在真实项目中对着HTML页面手足无措——这太正常了。就像背熟了游泳理论的人第一次下水,真正需要的是在真实场景中建立肌肉记忆。下面这5个精心设计的微型项目,将带你在SpringBoot环境中用Thymeleaf构建一个完整的管理后台,每个功能点都对应着实际开发中的高频需求。
1. 用户档案卡片的动态渲染
先从一个看似简单却暗藏玄机的需求开始:根据用户数据动态生成档案卡片。新建UserController添加测试数据:
@GetMapping("/users") public String showUsers(Model model) { List<User> users = Arrays.asList( new User("张三", 28, "developer", LocalDate.now().minusYears(2)), new User("李四", 35, "manager", LocalDate.now().minusMonths(6)), new User("王五", 22, "intern", LocalDate.now()) ); model.addAttribute("userList", users); return "user-cards"; }在HTML中,我们用Thymeleaf实现三种关键渲染技术:
<div class="card" th:each="user : ${userList}"> <!-- 条件渲染职位标签 --> <span th:classappend="${user.role == 'manager'} ? 'tag-blue' : ${user.role == 'developer'} ? 'tag-green' : 'tag-gray'" th:text="${user.role}"></span> <!-- 日期格式化 --> <p>入职时间:[[${#temporals.format(user.joinDate, 'yyyy年MM月dd日')}]]</p> <!-- 复合属性展示 --> <div th:text="|${user.name} (${user.age}岁)|"></div> </div>避坑指南:
- 当遇到
Could not parse as expression错误时,检查是否漏掉了|管道符 - 日期格式化前确保Model中的是
java.time对象而非字符串 th:classappend比直接操作class属性更安全
2. 智能数据表格与筛选器
管理后台最常见的功能非数据表格莫属。我们升级用户列表为可交互表格:
<!-- 筛选表单 --> <form th:action="@{/users}" method="get"> <input type="text" name="nameFilter" th:value="${param.nameFilter}"> <select name="roleFilter" th:value="${param.roleFilter}"> <option value="">所有角色</option> <option th:each="role : ${allRoles}" th:value="${role}" th:text="${role}"></option> </select> </form> <!-- 动态表格 --> <table> <tr th:each="user, stat : ${filteredUsers}"> <td th:text="${stat.count}"></td> <td th:text="${user.name}"></td> <td> <span th:switch="${user.role}"> <span th:case="'admin'" class="icon-shield"></span> <span th:case="'manager'" class="icon-star"></span> <span th:case="*" class="icon-user"></span> </span> </td> </tr> </table>后端控制器需要处理筛选参数:
@GetMapping("/users") public String filterUsers( @RequestParam(required = false) String nameFilter, @RequestParam(required = false) String roleFilter, Model model) { List<User> filtered = userService.findByFilters(nameFilter, roleFilter); model.addAttribute("filteredUsers", filtered); return "user-table"; }高级技巧:
- 使用
th:with局部变量简化重复表达式 th:attr动态设置自定义数据属性- 通过
#lists.isEmpty()判断空列表状态
3. 多步骤表单与数据绑定
实现一个用户注册流程,展示Thymeleaf的表单绑定能力:
<!-- 第一步:基本信息 --> <form th:action="@{/register}" th:object="${userForm}" method="post"> <input type="text" th:field="*{name}" placeholder="姓名"> <div th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div> <input type="email" th:field="*{email}"> <div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div> </form> <!-- 第二步:偏好设置(同一表单对象) --> <form th:action="@{/register/step2}" th:object="${userForm}" method="post"> <select th:field="*{preferredLanguage}"> <option th:each="lang : ${languages}" th:value="${lang.code}" th:text="${lang.name}"></option> </select> <div th:each="topic : ${allTopics}"> <input type="checkbox" th:field="*{interestedTopics}" th:value="${topic.id}"> <label th:text="${topic.name}"></label> </div> </form>对应的Spring控制器:
@PostMapping("/register") public String processStep1(@Valid UserForm form, BindingResult result) { if (result.hasErrors()) { return "register-step1"; } return "redirect:/register/step2"; } @GetMapping("/register/step2") public String showStep2(Model model) { model.addAttribute("languages", languageService.getAll()); model.addAttribute("allTopics", topicService.getTopics()); return "register-step2"; }关键点:
th:field自动绑定到表单对象属性th:errors显示验证错误信息- 多步骤表单保持相同
th:object引用 - 复选框绑定会自动处理数组转换
4. 动态导航与权限控制
根据用户权限动态生成侧边栏菜单:
<div class="sidebar" th:with="user=${session.user}"> <div th:each="menu : ${allMenus}" th:if="${#authorization.expression(menu.permission)}"> <a th:href="@{${menu.url}}" th:text="${menu.title}"></a> <!-- 嵌套子菜单 --> <div th:if="${menu.children}" class="submenu"> <a th:each="sub : ${menu.children}" th:href="@{${sub.url}}" th:text="${sub.title}" th:if="${#authorization.expression(sub.permission)}"></a> </div> </div> </div>安全配置类需要配合使用:
@Configuration public class SecurityConfig { @Bean public SpringSecurityDialect securityDialect() { return new SpringSecurityDialect(); } }实现细节:
- 使用
th:with存储频繁访问的会话数据 - Spring Security集成需要添加依赖:
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency> - 菜单数据建议缓存优化性能
5. 实时搜索与AJAX集成
最后我们实现一个无刷新搜索功能,展示Thymeleaf与现代前端技术的融合:
<!-- 搜索框 --> <input type="text" id="searchInput" placeholder="实时搜索..."> <!-- 结果容器 --> <div id="liveResults"> <!-- 初始状态显示全部 --> <div th:replace="~{fragments/user-list :: ${defaultUsers}}"></div> </div> <script th:inline="javascript"> document.getElementById('searchInput').addEventListener('input', function() { fetch('/api/users/search?term=' + encodeURIComponent(this.value)) .then(response => response.text()) .then(html => { document.getElementById('liveResults').innerHTML = html; }); }); </script>对应的片段模板fragments/user-list.html:
<div th:fragment="user-list(users)"> <div th:each="user : ${users}" class="search-result"> <h3 th:text="${user.name}"></h3> <p th:text="${user.email}"></p> </div> <!-- 空状态提示 --> <div th:if="${#lists.isEmpty(users)}" class="empty-tip"> 没有找到匹配的用户 </div> </div>后端控制器提供两种响应:
@GetMapping("/api/users/search") public String searchUsers(@RequestParam String term, Model model) { model.addAttribute("users", userService.search(term)); return "fragments/user-list :: user-list"; }性能优化建议:
- 添加防抖(debounce)减少请求频率
- 使用
th:remove优化初始加载性能 - 考虑WebSocket实现真正实时更新
经过这五个项目的实战,你会发现Thymeleaf的th:属性就像乐高积木的连接件,让静态HTML获得了动态组装的能力。这种服务端渲染的方式在现代前端框架盛行的今天依然有其不可替代的优势——特别是当你需要快速交付、SEO友好或与Spring生态深度集成时。
