Thymeleaf 3.0 官方使用指南中文版
Thymeleaf 快速入门与实践
1. Thymeleaf 简介
1.1 什么是 Thymeleaf?
Thymeleaf 是一款现代服务器端 Java 模板引擎,专为 Web 和独立环境设计。它能够处理 HTML、XML、JavaScript、CSS 以及纯文本等多种格式。
其核心理念是创建优雅且易于维护的模板。Thymeleaf 采用"自然模板"概念,将业务逻辑注入模板时,不会破坏模板作为设计原型的功能。这促进了设计与开发团队之间的沟通与协作。
Thymeleaf 从设计之初就考虑了对 Web 标准的支持,特别是 HTML5,允许你创建完全符合验证标准的模板。
1.2 支持的模板类型
Thymeleaf 开箱即用支持六种模板模式:
- HTML
- XML
- TEXT
- JAVASCRIPT
- CSS
- RAW
其中,HTML 和 XML 为标记模板模式,TEXT、JAVASCRIPT 和 CSS 为文本模板模式,RAW 则为无操作模式。
HTML 模式:接受任何类型的 HTML 输入,包括 HTML5、HTML4 和 XHTML。不会执行验证或格式检查,会尽可能保留原样。
XML 模式:要求输入必须是格式良好的 XML 代码,否则解析器会抛出异常。
TEXT 模式:允许使用特殊语法处理非标记性质的模板,如纯文本邮件。
JAVASCRIPT 模式:允许在 JS 文件中使用模型数据,并提供专门的转义和脚本处理。
CSS 模式:用于处理 CSS 文件,同样采用文本模式语法。
RAW 模式:不进行任何处理,用于将外部资源原样插入。
1.3 方言:标准方言
Thymeleaf 是一个高度可扩展的模板引擎。处理标记工件的对象称为处理器,而处理器的集合组成了方言。核心库提供了"标准方言",可满足大多数需求。
标准方言的大多数处理器是属性处理器,这使得浏览器能直接显示 HTML 模板文件,因为浏览器会忽略这些额外属性。例如,使用 JSP 的代码可能无法被浏览器直接显示:
<form:inputText name="userName" value="${user.name}" />
而 Thymeleaf 的等价实现,浏览器可以正常显示:
<input type="text" name="userName" value="James Carrot" th:value="${user.name}" />
这种"自然模板"能力,让设计与开发团队能处理同一个模板文件,减少了将原型转换为工作模板的工作量。
2. 示例项目:虚拟杂货店
本文的示例代码可在 Good Thymes Virtual Grocery GitHub 仓库中找到。
2.1 网站架构
演示应用是一个虚拟杂货店网站,它包含简单的模型实体:产品、客户、订单以及产品评论。
服务层包含 ProductService 等对象,用于提供数据。Web 层通过一个过滤器,根据请求 URL 将执行委托给 Thymeleaf 命令。
private boolean process(HttpServletRequest request, HttpServletResponse response)
throws ServletException {
try {
if (request.getRequestURI().startsWith("/css") ||
request.getRequestURI().startsWith("/images") ||
request.getRequestURI().startsWith("/favicon")) {
return false;
}
IGTVGController controller = this.application.resolveControllerForRequest(request);
if (controller == null) {
return false;
}
ITemplateEngine templateEngine = this.application.getTemplateEngine();
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
controller.process(request, response, this.servletContext, templateEngine);
return true;
} catch (Exception e) {
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (final IOException ignored) {
// Ignore
}
throw new ServletException(e);
}
}
IGTVGController 接口定义如下:
public interface IGTVGController {
public void process(
HttpServletRequest request, HttpServletResponse response,
ServletContext servletContext, ITemplateEngine templateEngine);
}
2.2 配置模板引擎
在 GTVGApplication 类中初始化模板引擎:
public class GTVGApplication {
private final TemplateEngine templateEngine;
public GTVGApplication(final ServletContext servletContext) {
super();
ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver(servletContext);
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCacheTTLMs(Long.valueOf(3600000L));
templateResolver.setCacheable(true);
this.templateEngine = new TemplateEngine();
this.templateEngine.setTemplateResolver(templateResolver);
}
}
模板解析器
ServletContextTemplateResolver 从 Servlet 上下文中获取模板文件。通过配置前缀和后缀,可以将模板名称转换为实际资源路径。例如,模板名称 "product/list" 会被映射到 /WEB-INF/templates/product/list.html。
模板引擎
创建 TemplateEngine 实例并为其设置模板解析器即可。
3. 文本处理
3.1 国际化的欢迎页面
创建首页 /WEB-INF/templates/home.html:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body>
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
</body>
</html>
这里使用了 th:text 属性和 #{...} 表达式来获取国际化消息。
也可以使用 HTML5 友好的 data- 前缀语法:
<p data-th-text="#{home.welcome}">Welcome to our grocery store!</p>
使用 Context
创建 HomeController 并使用 WebContext:
public class HomeController implements IGTVGController {
public void process(
final HttpServletRequest request, final HttpServletResponse response,
final ServletContext servletContext, final ITemplateEngine templateEngine)
throws Exception {
WebContext ctx =
new WebContext(request, response, servletContext, request.getLocale());
templateEngine.process("home", ctx, response.getWriter());
}
}
WebContext 允许访问请求参数、会话和应用程序属性。例如,${param.x}、${session.x} 等。
3.2 变量与未转义文本
使用 th:utext 输出未转义的 HTML:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>
在控制器中添加变量:
ctx.setVariable("today", dateFormat.format(cal.getTime()));
在模板中显示:
<p>Today is: <span th:text="${today}">13 February 2011</span></p>
4. 标准表达式语法
Thymeleaf 标准表达式支持多种类型:
- 简单表达式:
${...}、*{...}、#{...}、@{...}、~{...} - 字面量:文本、数字、布尔、null、文字标记
- 运算符:字符串拼接、算术、布尔、比较、条件等
4.1 消息表达式
支持带参数的消息:
home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!
<p th:utext="#{home.welcome(${session.user.name})}">Welcome!</p>
4.2 变量表达式
基于 OGNL 表达式访问对象属性:
${person.father.name}
${person['father']['name']}
${personsArray[0].name}
${person.createCompleteNameWithSeparator('-')}
表达式基本对象
#ctx:上下文对象#vars:上下文变量#locale:语言环境#request、#response、#session、#servletContext:Web 上下文对象
表达式工具对象
#dates、#calendars:日期格式化#numbers:数字格式化#strings:字符串操作#lists、#sets、#maps:集合操作#aggregates:聚合函数
例如,在模板中直接格式化日期:
<span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
4.3 选择表达式
使用 th:object 设置选定对象,然后用 *{...} 访问属性:
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
</div>
4.4 链接表达式
使用 @{...} 语法:
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
支持路径变量:
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
4.5 片段表达式
用于在模板中引用和移动片段:
<div th:insert="~{commons :: main}">...</div>
4.6 文字与运算符
字符串拼接:+|Welcome, ${user.name}!|
条件表达式:${row.even}? 'even' : 'odd'
默认值表达式(Elvis 操作符):*{age}?: '(no age specified)'
无操作令牌 (_):保留原型文本作为默认值。
4.7 数据转换
双括号语法 ${{...}} 调用转换服务:
<td th:text="${{user.lastAccessDate}}">...</td>
5. 设置属性值
5.1 `th:attr` 属性
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</form>
5.2 特定属性处理器
更推荐使用特定属性,如 th:value、th:action、th:href 等。
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
5.3 追加和前置
<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />
5.4 布尔属性
<input type="checkbox" name="active" th:checked="${user.active}" />
6. 迭代
6.1 基本用法
使用 th:each:
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
6.2 迭代状态
状态变量提供 index、count、even、odd、first、last 等属性:
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
...
</tr>
6.3 惰性数据加载
实现 ILazyContextVariable 接口,可在需要时才加载数据:
context.setVariable("users", new LazyContextVariable<List<User>>() {
@Override
protected List<User> loadValue() {
return databaseRepository.findAllUsers();
}
});
7. 条件判断
7.1 `th:if` 和 `th:unless`
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
7.2 `th:switch` / `th:case`
<div th:switch="${user.role}">
<p th:case="'admin'">Admin</p>
<p th:case="*">Other</p>
</div>
8. 模板布局
8.1 定义和引用片段
使用 th:fragment 定义片段:
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
在另一个模板中通过 th:insert 或 th:replace 引入:
<div th:insert="~{footer :: copy}"></div>
8.2 参数化片段
<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>
调用时传递参数:
<div th:replace="::frag (${value1},${value2})">...</div>
8.3 灵活布局
片段可以作为参数传递,实现高度灵活的布局:
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">Default Title</title>
<th:block th:replace="${links}" />
</head>
调用时使用片段表达式:
<head th:replace="base :: common_header(~{::title},~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
</head>
9. 局部变量
使用 th:with 定义局部变量:
<div th:with="firstPer=${persons[0]}">
<span th:text="${firstPer.name}">Julius Caesar</span>
</div>
10. 属性优先级
Thymeleaf 属性按优先级执行,顺序为:片段包含、遍历、条件判断、变量定义、属性修改、文本替换等。
11. 注释与块
- 标准注释:
<!-- ... -->,不会处理。 - 解析器级注释:
<!--/* ... */-->,在解析时删除。 - 原型注释:
<!--/*/ ... /*/-->,处理时取消注释。 - th:block:属性容器,执行后消失。
12. 内联
12.1 表达式内联
<p>Hello, [[${session.user.name}]]!</p>
<p>The message is [[${msg}]]</p>
[[...]] 转义输出,[(...)] 不转义。
12.2 JavaScript 内联
<script th:inline="javascript">
var username = [[${session.user.name}]];
</script>
12.3 CSS 内联
<style th:inline="css">
.[[${classname}]] { text-align: [[${align}]]; }
</style>
13. 文本模板模式
文本模式 (TEXT、JAVASCRIPT、CSS) 无需标签,但提供 [# ...] 标签语法:
[# th:each="item : ${items}"]
- [(${item})]
[/]
14. 更多示例页面
14.1 订单列表
<tr th:each="o : ${orders}" th:class="${oStat.odd}? 'odd'">
<td th:text="${#calendars.format(o.date,'dd/MMM/yyyy')}">13 jan 2011</td>
<td th:text="${o.customer.name}">Frederic Tomato</td>
<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td>
</tr>
14.2 订单详情
<body th:object="${order}">
<div th:object="*{customer}">
<p>Name: <span th:text="*{name}">Frederic Tomato</span></p>
</div>
</body>
15. 配置详解
15.1 模板解析器
Thymeleaf 提供了多种预置模板解析器,如 ClassLoaderTemplateResolver、FileTemplateResolver、UrlTemplateResolver 和 StringTemplateResolver。它们支持配置前缀、后缀、编码、缓存等。
可以链式使用多个解析器,设置优先级和前序关系。
15.2 消息解析器
标准消息解析器按语言顺序查找 properties 文件。可以通过 setMessageResolver() 配置。
15.3 转换服务
通过 IStandardConversionService 接口自定义转换。
15.4 日志
Thymeleaf 基于 SLF4J 日志框架,提供配置日志级别和特定类别日志的能力。
16. 模板缓存
Thymeleaf 默认缓存解析后的模板,可通过 setCacheable(false) 禁用。可通过 StandardCacheManager 配置缓存大小。
17. 解耦模板逻辑
通过 .th.xml 文件将逻辑与模板分离,使用 <attr sel="..." th:.../> 在外部注入属性。
附录 A:表达式基本对象
#ctx、#locale、#request、#session、#servletContext 等,提供对上下文和 Web 对象的便捷访问。
附录 B:表达式工具对象
#dates、#numbers、#strings、#lists、#maps、#aggregates 等,提供丰富的实用方法。
附录 C:标记选择器语法
支持类似 CSS 和 XPath 的选择器,如 //div[@class='content']、div.content、#oneid、th:ref 等。