Thymeleaf是一个现代化的服务器端Java模板引擎,适用于Web和独立环境,能够处理HTML、XML、JavaScript、CSS甚至纯文本。
核心特点:
特性 | Thymeleaf | JSP | Freemarker | Velocity |
---|---|---|---|---|
自然模板 | ✔️ | ❌ | ❌ | ❌ |
Spring集成 | ✔️ | ✔️ | ✔️ | ✔️ |
学习曲线 | 中等 | 低 | 低 | 低 |
性能 | 中等 | 高 | 高 | 中 |
HTML5支持 | ✔️ | ❌ | ❌ | ❌ |
静态原型 | ✔️ | ❌ | ❌ | ❌ |
模板(Template):包含静态内容和动态占位符的文件
表达式(Expression):用于访问和操作模型数据的语法
处理器(Processor):处理特定Thymeleaf属性的组件
方言(Dialect):一组处理器和表达式的集合
在pom.xml
中添加Thymeleaf starter依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
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
控制器代码:
@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-01span>p>
<div th:if="${not #strings.isEmpty(message)}">
<p th:text="'消息长度: ' + ${#strings.length(message)}">p>
div>
body>
html>
Spring Boot默认静态资源位置:
classpath:/static/
classpath:/public/
classpath:/resources/
在Thymeleaf中引用静态资源:
<link th:href="@{/css/style.css}" rel="stylesheet">
<script th:src="@{/js/app.js}">script>
<img th:src="@{/images/logo.png}" alt="Logo">
表达式类型 | 语法示例 | 说明 |
---|---|---|
变量表达式 | ${user.name} |
访问模型属性 |
选择表达式 | *{name} |
在选定对象上执行 |
消息表达式 | #{home.welcome} |
国际化消息 |
链接表达式 | @{/user/list} |
URL构建 |
片段表达式 | ~{footer :: copy} |
模板片段引用 |
属性 | 说明 | 示例 | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
th:text | 设置文本内容 |
|
||||||||||||||||||
th:utext | 设置文本内容(不转义HTML) |
|
||||||||||||||||||
th:value | 设置表单元素值 |
|
||||||||||||||||||
th:each | 循环 |
|
变量表达式:
<p>用户名: <span th:text="${user.username}">默认用户名span>p>
<p>年龄: <span th:text="${user.age}">0span>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}">1td>
<td th:text="${product.name}">产品名td>
<td th:text="${product.price}">价格td>
<td th:text="${iterStat.odd} ? '奇数行' : '偶数行'">行状态td>
tr>
table>
URL链接:
<a th:href="@{/products}">产品列表a>
<a th:href="@{/products/{id}(id=${product.id})}">产品详情a>
<a th:href="@{/search(keyword=${keyword},page=1)}">搜索a>
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-01p>
<p th:text="${#strings.toUpperCase(user.name)}">张三p>
<p th:text="${#numbers.formatDecimal(product.price, 1, 2)}">99.99p>
控制器:
@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>
实体类:
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";
}
表单模板:
<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>
控制器:
@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>
内联表达式允许在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>
定义基础模板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>
定义片段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>
<div th:with="firstUser=${users[0]}">
<p th:text="${firstUser.name}">用户名p>
div>
在开发阶段,建议关闭缓存以便实时查看修改:
spring.thymeleaf.cache=false
在生产环境,应该开启缓存提高性能:
spring.thymeleaf.cache=true
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=电子邮箱
<h1 th:text="#{home.welcome}">Welcomeh1>
<label th:text="#{user.name}">Usernamelabel>
<input type="text" name="username">
<p th:text="#{welcome.message(${user.name})}">Hello, User!p>
控制器:
@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')}">Englisha>
策略 | 说明 | 适用场景 |
---|---|---|
全缓存 | 所有模板都缓存 | 生产环境 |
无缓存 | 不缓存任何模板 | 开发环境 |
部分缓存 | 只缓存特定模板 | 特殊需求 |
在application.properties
中启用监控:
logging.level.org.thymeleaf=DEBUG
问题:模板语法错误导致无法解析
解决:检查错误信息,通常Thymeleaf会给出详细的行号和错误原因
问题:CSS/JS/图片无法加载
解决:
static
或public
目录th:href="@{/path/to/resource}"
问题:${...}
或*{...}
表达式没有渲染
解决:
xmlns:th="http://www.thymeleaf.org"
声明问题:表单提交后模型属性没有绑定
解决:
th:object
属性th:field
属性@ModelAttribute
src/main/resources/
├── static/ # 静态资源
│ ├── css/
│ ├── js/
│ └── images/
├── templates/ # 模板文件
│ ├── fragments/ # 片段
│ ├── layout.html # 基础布局
│ ├── home.html # 首页
│ └── user/ # 用户相关模板
└── messages.properties # 国际化文件
user-profile.html
fragments
目录,如fragments/nav.html
@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";
}
}
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>IDth>
<th>用户名th>
<th>邮箱th>
<th>角色th>
<th>操作th>
tr>
thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.id}">1td>
<td th:text="${user.username}">admintd>
<td th:text="${user.email}">[email protected]td>
<td th:text="${user.role}">ADMINtd>
<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>
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生态中的首选模板引擎,提供了强大的功能和良好的开发体验。通过本文的学习,你应该能够:
头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,后期会有更多的干货以及资料下载。