Spring Boot整合Thymeleaf模板开发:从入门到精通

一、Thymeleaf基础概念

1 什么是Thymeleaf

Thymeleaf是一个现代化的服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML、XML、JavaScript、CSS甚至纯文本。

核心特点

  • 自然模板:Thymeleaf模板可以作为静态原型直接在浏览器中打开
  • Spring集成:与Spring框架无缝集成,特别是Spring MVC
  • 丰富的表达式语言:支持Spring EL表达式
  • 模块化:支持模板布局和片段重用
  • 国际化支持:内置国际化支持

2. Thymeleaf vs 其他模板引擎

特性ThymeleafJSPFreemarkerVelocity
自然模板✔️
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.prefixclasspath:/templates/模板文件位置
spring.thymeleaf.suffix.html模板文件后缀
spring.thymeleaf.modeHTML模板模式
spring.thymeleaf.cachetrue(生产)/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)}
#mapsMap操作${#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. 模板优化技巧

  1. 减少模板复杂度:避免过度嵌套和复杂逻辑
  2. 使用片段重用:将重复部分提取为片段
  3. 合理使用布局:使用布局减少重复代码
  4. 避免大循环:大数据集考虑分页
  5. 减少内联脚本:将JavaScript移到外部文件

3. 监控模板渲染时间

application.properties中启用监控:

logging.level.org.thymeleaf=DEBUG

八、常见问题与解决方案

1. 模板解析错误

问题:模板语法错误导致无法解析
解决:检查错误信息,通常Thymeleaf会给出详细的行号和错误原因

2. 静态资源404

问题:CSS/JS/图片无法加载
解决

  • 确保资源放在staticpublic目录
  • 使用正确的引用方式: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的各个方面,从基础配置到高级特性,包括:

  1. 基础集成:如何将Thymeleaf添加到Spring Boot项目
  2. 模板语法:各种表达式和属性的使用方法
  3. 表单处理:表单绑定、验证和各类表单元素的使用
  4. 高级特性:布局、片段、内联表达式等
  5. 国际化:多语言支持实现
  6. 性能优化:缓存策略和模板优化技巧
  7. 最佳实践:项目结构、命名约定和安全考虑

Thymeleaf作为Spring生态中的首选模板引擎,提供了强大的功能和良好的开发体验。通过本文的学习,你应该能够:

  • 熟练使用Thymeleaf开发各种Web页面
  • 实现复杂的页面布局和组件复用
  • 处理表单和用户输入验证
  • 构建国际化的Web应用
  • 优化模板性能

头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clf丶忆笙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值