Article

模板引擎 Thymeleaf

更新于:2026-07-15

第一章:Thymeleaf 入门基础

1.1 什么是 Thymeleaf

概念名称说明注意事项
Thymeleaf一种适用于 Java 应用的现代服务器端模板引擎,支持 HTML5、XML、JavaScript、CSS 等模板类型。主要用于在服务端动态生成 HTML 页面。不依赖特定 Web 框架,但与 Spring Boot 集成最紧密。
模板引擎负责将数据模型与模板文件结合,生成最终输出(如 HTML)的组件。Thymeleaf 在运行时解析模板并注入数据。模板文件本身是合法的 HTML,可直接在浏览器中预览(自然模板特性)。
自然模板(Natural Templates)Thymeleaf 模板在未渲染状态下仍能被浏览器正确显示,便于前端开发与协作。需合理使用 th:* 属性,避免破坏原始 HTML 结构。

1.2 Thymeleaf 的核心特性

特性名称说明注意事项
支持多种模板模式包括 HTML、XML、TEXT、JAVASCRIPT、CSS、RAW 六种模板模式。默认为 HTML 模式;其他模式需显式配置或通过文件扩展名识别。
标准方言(Standard Dialect)提供表达式、流程控制、属性设置等核心功能,基于 OGNL 或 SpringEL(Spring 环境下)。在 Spring Boot 中默认使用 SpringEL 表达式。
可扩展性支持自定义方言(Dialect),可添加新的属性、表达式或处理器。自定义方言需实现 IProcessorDialect 接口。
无侵入性模板中使用 th:* 属性,不影响原始 HTML 结构,便于前后端并行开发。避免在 th:* 属性中写复杂逻辑,保持模板简洁。
国际化支持内置 #messages 工具对象,支持多语言消息资源加载。需配置 message.properties 文件及 LocaleResolver
安全性自动对输出内容进行 HTML 转义,防止 XSS 攻击。如需输出原始 HTML,需使用 th:utext 而非 th:text

1.3 Thymeleaf 与 JSP、FreeMarker 的对比

对比维度ThymeleafJSP(JavaServer Pages)FreeMarker
模板语法基于属性(th:text 等),HTML 原生兼容嵌入 Java 代码(<% %>)或 JSTL 标签使用自定义指令(如 <#if>$ {}
自然模板支持,未渲染时可直接在浏览器查看不支持,需服务器渲染不支持
性能中等,首次渲染较慢,后续缓存加速较快(编译为 Servlet)较快,模板预编译
与 Spring 集成官方推荐,无缝集成支持,但已逐渐淘汰支持良好
学习曲线较低,HTML 开发者友好中等,需了解 Servlet 生命周期中等,需学习专属语法
响应式支持支持(Thymeleaf 3.0+ 与 WebFlux 兼容)不支持(基于 Servlet,阻塞式)有限支持(需额外适配)
社区活跃度高(Spring 生态主力模板引擎)低(Oracle 已停止更新)中等

1.4 环境搭建(Spring Boot 集成)

步骤名称操作细节注意事项
添加依赖pom.xml 中添加 spring-boot-starter-thymeleaf(见下方代码示例 1)不要手动添加 thymeleaf-spring5 依赖,starter 已包含。
配置模板路径默认模板目录为 src/main/resources/templates,无需额外配置。若需修改,可在 application.yml 中设置 spring.thymeleaf.prefix
创建控制器编写 Controller 返回视图名称(见下方代码示例 2)方法返回值为模板文件名(不含 .html 后缀)。
创建模板文件templates 目录下创建 hello.html(见下方代码示例 3)必须声明 xmlns:th 命名空间以启用 IDE 提示(非强制运行,但推荐)。
启动应用并访问运行 Spring Boot 应用,访问 http://localhost:8080/hello 查看渲染结果。确保端口未被占用,默认 8080;若修改需同步调整 URL。
关闭模板缓存(开发)application.properties 中添加 spring.thymeleaf.cache=false仅用于开发环境,生产环境应开启缓存提升性能。

代码示例 1:添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

代码示例 2:创建控制器

@Controller
public class HomeController {
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello Thymeleaf!");
        return "hello";
    }
}

代码示例 3:创建模板文件

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Hello</title></head>
<body>
<p th:text="$ {message}">Default text</p>
</body>
</html>

第二章:Thymeleaf 基础语法

2.1 标准方言(Standard Dialect)概述

概念名称说明注意事项
标准方言(Standard Dialect)Thymeleaf 默认提供的核心功能集合,包含表达式、流程控制、属性处理等,适用于 HTML/XML 模板。在 Spring Boot 中自动启用,无需额外配置。
SpringStandardDialectSpring 环境下使用的标准方言变体,使用 SpringEL(而非 OGNL)解析表达式。所有 $ {} 表达式均通过 Spring 的 Expression Language 解析。
方言作用域提供 th:* 命名空间下的所有属性(如 th:textth:each 等)及内置对象(#dates#strings 等)。自定义方言可扩展此功能,但标准方言已覆盖绝大多数场景。
模板处理器负责解析模板中的 th:* 属性并执行相应逻辑(如变量替换、条件判断等)。处理顺序由 Thymeleaf 内部定义,开发者无需干预。

2.2 表达式语法($ {}*{}#{}@{}~{}

表达式类型语法示例用途代码示例注意事项
变量表达式$ {user.name}访问上下文中的变量(Model 中的数据),支持嵌套属性和方法调用。<p th:text="$ {user.email}"></p>若 user 为 null,会抛出异常;可配合 ?:*{} 使用。
选择表达式*{name}在已选定对象(通过 th:object)上访问属性,简化写法。见下方代码示例 1必须在 th:object 作用域内使用,否则等同于 $ {}
工具对象#{#dates.format(now, 'yyyy')}调用 Thymeleaf 内置工具对象(#dates#strings#numbers 等)。<span th:text="$ {#dates.format(#dates.createNow(), 'yyyy-MM-dd')}"></span>工具对象前需加 #,且不能省略括号。
链接 URL@{/user/profile(id=3)}构造应用上下文相关的 URL,支持路径变量和查询参数。<a th:href="@{/order/details(id= $ {orderId})}">View Order</a>自动处理 context-path;相对路径以 / 开头。
片段表达式~{fragments/footer :: copy}引用模板片段(用于 th:insert/th:replace),支持参数传递。<div th:insert="~{common :: footer(copyrightYear=2026)}"></div>片段文件路径相对于 templates 目录。

代码示例 1:选择表达式

<div th:object="$ {user}">
    <p th:text="*{email}"></p>
</div>

2.3 文本与字面量处理

处理方式语法/方法用途代码示例注意事项
字面量文本'Hello'在表达式中表示字符串常量。<p th:text="'Welcome, ' + $ {user.name}"></p>字符串必须用单引号包裹。
数字字面量42, 3.14表示数值常量。<span th:text="$ {price > 100 ? 'Expensive' : 'Cheap'}"></span>支持整数和浮点数。
布尔字面量true, false表示布尔值。<div th:if="$ {isActive == true}">Active</div>可直接使用 true/false,无需引号。
文本拼接+连接字符串、变量或表达式结果。<p th:text="$ {'Total: ' + #numbers.formatDecimal(total, 1, 2)}"></p>自动类型转换,但建议显式格式化。
转义文本输出th:text输出内容自动 HTML 转义(防止 XSS)。<p th:text="$ {userInput}">Default</p>若 userInput 含 <script>,将被转义。
非转义文本输出th:utext输出原始 HTML(不转义)。<div th:utext="$ {htmlContent}"></div>仅用于可信内容,否则有 XSS 风险。

2.4 属性设置(th:textth:valueth:attr 等)

属性名称语法示例用途代码示例注意事项
th:textth:text="$ {message}"设置元素的文本内容(替换标签内所有内容)。<h1 th:text="$ {title}">Fallback Title</h1>替换整个 innerHTML 文本部分。
th:valueth:value="$ {inputValue}"设置表单元素(如 inputtextarea)的 value 属性。<input type="text" th:value="$ {username}" />适用于非绑定表单;绑定表单推荐 th:field
th:attrth:attr="data-id= $ {id},title= $ {tooltip}"动态设置任意 HTML 属性。<button th:attr="disabled= $ {isDisabled}, class='btn'">Click</button>可同时设置多个属性,用逗号分隔。
th:alt/th:titleth:alt="$ {imageAlt}"专用于设置 alttitle 等常用属性(语法糖)。<img src="logo.png" th:alt="$ {siteName}" />等价于 th:attr="alt= $ {siteName}",但更简洁。
th:placeholderth:placeholder="#{msg.placeholder}"设置 inputplaceholder 属性。<input type="email" th:placeholder="#{email.hint}" />常与国际化消息配合使用。
th:styleth:style="'color:' + $ {color}"动态设置 style 属性。<span th:style="$ {'font-size: ' + fontSize + 'px'}">Text</span>注意 CSS 注入风险,确保值可信。
th:classth:class="$ {isActive ? 'active' : 'inactive'}"动态设置 class 属性。<div th:class="$ {error ? 'alert alert-danger' : ''}">...</div>可结合条件表达式动态切换样式。
th:src/th:hrefth:src="@{/images/logo.png}"设置资源路径(自动处理 context-path)。<script th:src="@{/js/app.js}"></script>推荐使用 @{...} 构造路径,避免硬编码。

第三章:流程控制与逻辑处理

3.1 条件判断(th:ifth:unlessth:switch/th:case

属性/结构语法示例用途代码示例注意事项
th:ifth:if="$ {user != null}"当表达式为 true 时,渲染该元素;否则完全移除。见下方代码示例 1表达式结果需为布尔值或可转为布尔(null/empty 视为 false)。
th:unlessth:unless="$ {isEmpty}"当表达式为 false 时渲染元素(即 if 的反向)。<button th:unless="$ {isSubmitted}">Submit</button>等价于 th:if="$ {!isEmpty}",但语义更清晰。
th:switchth:switch="$ {role}"多分支条件判断,配合 th:case 使用。见下方代码示例 2必须与 th:case 配合使用;* 表示 default 分支。
th:caseth:case="'ADMIN'"定义 switch 中的一个分支,支持字面量、变量或通配符 *(见上方代码示例 2)字符串需加单引号;数字无需引号(如 th:case="1")。
嵌套条件th:if 内部再使用 th:if实现复杂逻辑判断。见下方代码示例 3避免过深嵌套,建议在后端预处理逻辑。

代码示例 1:th:if

<div th:if="$ {isAdmin}">
    <p>Admin Panel</p>
</div>

代码示例 2:th:switch/th:case

<div th:switch="$ {user.role}">
    <p th:case="'ADMIN'">Administrator</p>
    <p th:case="'USER'">Regular User</p>
    <p th:case="*">Unknown Role</p>
</div>

代码示例 3:嵌套条件

<div th:if="$ {user != null}">
    <span th:if="$ {user.active}">Active</span>
</div>

3.2 循环遍历(th:each

属性/概念语法示例用途代码示例注意事项
th:eachth:each="item : $ {items}"遍历集合、数组、Map 等可迭代对象。见下方代码示例 1支持 List、Set、Array、Map、Iterable 等。
状态变量itemStat(自动注入)提供当前迭代状态信息(index、count、even、first、last 等)。见下方代码示例 2状态变量名可自定义(如 iterStat),默认为 变量名 + Stat
状态属性index, count, size, even, odd, first, last分别表示:从0开始索引、从1开始计数、总数量、是否偶数行等。见下方代码示例 3st.size 返回集合大小;st.last 可用于添加分隔符等。
遍历 Mapth:each="entry : $ {userMap}"遍历时 entryMap.Entry,可用 entry.key / entry.value 访问。见下方代码示例 4不支持直接解构(如 key, value : map)。
空集合处理结合 th:if 或提供默认内容避免空列表导致 UI 异常。见下方代码示例 5可使用 #lists 工具对象判断空集合。

代码示例 1:基本遍历

<ul>
    <li th:each="name : $ {userList}" th:text="$ {name}"></li>
</ul>

代码示例 2:状态变量

<tr th:each="prod, iterStat : $ {products}">
    <td th:text="$ {iterStat.count}"></td>
    <td th:text="$ {prod.name}"></td>
</tr>

代码示例 3:状态属性(奇偶行样式)

<div th:each="msg, st : $ {messages}"
     th:class="$ {st.even} ? 'even-row' : 'odd-row'">
    <p th:text="$ {msg}"></p>
</div>

代码示例 4:遍历 Map

<dl th:each="e : $ {configMap}">
    <dt th:text="$ {e.key}"></dt>
    <dd th:text="$ {e.value}"></dd>
</dl>

代码示例 5:空集合处理

<div th:if="$ {#lists.isEmpty(products)}">No products available.</div>
<ul th:unless="$ {#lists.isEmpty(products)}">
    <li th:each="p : $ {products}">...</li>
</ul>

3.3 内联表达式与 JavaScript/CSS 中的使用(th:inline

属性/用法语法示例用途代码示例注意事项
th:inline="text"<span th:inline="text">Hello [[$ {name}]]!</span>在文本内容中直接使用 [[...]] 内联表达式,等价于 th:text见下方代码示例 1[[...]] 自动 HTML 转义;[($ {...})] 用于非转义(不推荐)。
th:inline="javascript"<script th:inline="javascript">...</script>在 JavaScript 代码块中使用内联表达式,输出 JSON 或变量。见下方代码示例 2表达式结果会自动转为 JSON 格式(如 List → JS Array)。
th:inline="css"<style th:inline="css">...</style>在 CSS 中动态插入值(较少使用)。见下方代码示例 3仅适用于简单值(颜色、尺寸等),避免复杂逻辑。
内联注释语法/[[$ {expr}]]/在 JS/CSS 中安全嵌入 Thymeleaf 表达式,防止语法冲突。(见上方 JavaScript/CSS 示例)注释形式确保即使模板未渲染,JS 仍合法(如 userId = null)。
禁用内联th:inline="none"显式关闭内联处理(默认在 script/style 中不启用)。<script th:inline="none">默认情况下,script 和 style 标签不启用内联,除非显式声明。

代码示例 1:文本内联

<p th:inline="text">Welcome, [[$ {user.name}]]! Your balance: [[$ {#numbers.formatCurrency(balance)}]].</p>

代码示例 2:JavaScript 内联

<script th:inline="javascript">
    var userId = /*[[$ {userId}]]*/ null;
    var userRoles = /*[[$ {roles}]]*/ [];
</script>

代码示例 3:CSS 内联

<style th:inline="css">
    .highlight { color: /*[[$ {themeColor}]]*/ red; }
</style>

第四章:模板复用与布局

4.1 片段引入(th:insertth:replaceth:include

属性名称语法示例用途代码示例注意事项
th:insertth:insert="~{fragments/footer :: copy}"将目标片段作为子节点插入到当前元素内部。见下方代码示例 1当前元素保留,片段内容作为其子元素插入,渲染为 <div><footer>...</footer></div>
th:replaceth:replace="~{fragments/header :: nav}"用目标片段完全替换当前元素。见下方代码示例 2原始标签被完全移除,仅保留片段内容,渲染为 <nav class="top-nav">...</nav>
th:includeth:include="~{fragments/sidebar :: menu}"(Thymeleaf 3.0 起已废弃)旧版行为类似 th:insert,但仅插入片段内容(不含外层标签)。见下方代码示例 3不推荐使用;Thymeleaf 3.0+ 中 th:include 行为等同于 th:insert,官方建议统一用 th:insert/th:replace
片段引用语法~{template :: selector}fragments/footer :: #main指定要引入的模板文件及其中的片段(通过 th:fragment 名称或 DOM 选择器)。<div th:insert="~{user/profile :: #contact-info}"></div>支持 CSS 选择器(如 #id.class),但推荐使用 th:fragment 显式定义。
带参数引入th:insert="~{frag :: alert(msg='Error')}"向被引入的片段传递参数。<div th:insert="~{common :: alert(type='danger', message= $ {errorMsg})}"></div>参数在片段内通过 $ {type}$ {message} 访问。

代码示例 1:th:insert

<!-- 使用前 -->
<div th:insert="~{common :: footer}"></div>
<!-- 渲染后 -->
<div><footer>...</footer></div>

代码示例 2:th:replace

<!-- 使用前 -->
<header th:replace="~{layout :: site-header}"></header>
<!-- 渲染后(假设 site-header 是 <nav class="top-nav">) -->
<nav class="top-nav">...</nav>

代码示例 3:th:include(已废弃)

<aside th:include="~{fragments :: sidebar-menu}"></aside>

4.2 使用 th:fragment 定义可复用模块

概念/用法语法示例用途代码示例注意事项
th:fragment<footer th:fragment="site-footer">...</footer>定义一个可被其他模板引用的片段。见下方代码示例 1片段名可包含参数列表(用于接收传入值)。
无名片段使用 DOM 选择器(如 #header不使用 th:fragment,直接通过 ID 或 class 引用 HTML 元素。见下方代码示例 2依赖结构稳定性,不如 th:fragment 显式可靠。
参数默认值在片段内部使用 ?: 提供默认值避免未传参时出错。见下方代码示例 3推荐为所有参数设置默认值以增强健壮性。
嵌套片段在 fragment 内部再引用其他 fragment构建模块化组件体系。见下方代码示例 4注意循环引用风险(A 引 B,B 又引 A)。
片段文件组织存放于 templates 目录下的独立文件提高可维护性,便于团队协作。文件路径:src/main/resources/templates/components/buttons.html建议按功能分类(如 layout、components、forms)。

代码示例 1:th:fragment 定义

<!-- common.html -->
<div th:fragment="card(title, content)">
    <h3 th:text="$ {title}"></h3>
    <p th:text="$ {content}"></p>
</div>

代码示例 2:无名片段(通过 ID 引用)

<!-- 定义 -->
<nav id="main-nav">...</nav>

<!-- 引用 -->
<div th:replace="~{layout :: #main-nav}"></div>

代码示例 3:参数默认值

<span th:fragment="label(text)"
      th:text="$ {text ?: 'Default Label'}"></span>

代码示例 4:嵌套片段

<div th:fragment="page">
    <div th:replace="~{layout :: header}"></div>
    <main>...</main>
    <div th:insert="~{layout :: footer}"></div>
</div>

4.3 布局继承与模板装饰(Layout Dialect 可选)

概念/方案说明代码示例注意事项
原生 Thymeleaf 布局通过 th:replace/th:insert 手动组合 header/content/footer 实现布局复用。见下方代码示例 1无需额外依赖,纯标准方言实现;需约定 fragment 名称(如 content)。
Thymeleaf Layout Dialect第三方扩展(如 nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect),提供 layout:decorator 等高级语法。见下方代码示例 2需添加 Maven 依赖;简化布局但增加外部依赖;Spring Boot 2.x+ 默认不集成。
装饰器模式优势自动将内容”注入”到布局模板的指定位置。见下方代码示例 3更接近传统模板继承(如 Django、Jinja2);但需学习额外语法。
选型建议简单项目用原生方案;复杂多页应用可考虑 Layout Dialect。Spring Boot 官方推荐优先使用标准方言;Layout Dialect 已多年未更新,兼容性需验证。
替代方案使用 WebJars + 前端框架(如 Vue/React)实现 SPA,后端仅提供 API。若项目前后端分离,Thymeleaf 布局意义降低;适用于服务端渲染(SSR)场景。

代码示例 1:原生 Thymeleaf 布局

<!-- base.html -->
<html>
<head>...</head>
<body>
<div th:replace="~{this :: content}"></div>
</body>
</html>

<!-- page.html -->
<html th:replace="~{base :: html}">
<th:block th:fragment="content">
    <h1>My Page</h1>
</th:block>
</html>

代码示例 2:Layout Dialect 布局

<!-- page.html -->
<html layout:decorator="layout/base">
<title layout:title-pattern="$CONTENT_TITLE - My Site">Page Title</title>
<body>
<h1>Content Here</h1>
</body>
</html>

代码示例 3:装饰器模式中的布局模板

<!-- base.html -->
<body>
<header>...</header>
<div layout:fragment="content"></div>
<footer>...</footer>
</body>

第五章:国际化与本地化

5.1 国际化消息配置

配置项/文件说明代码示例 / 文件内容注意事项
消息资源文件命名规则格式:messages[_语言_国家].properties,如 messages_zh_CN.properties见下方代码示例 1文件必须放在 src/main/resources 目录下;Spring Boot 自动加载。
支持的语言区域(Locale)通过 LocaleResolver 决定当前用户语言。Spring Boot 默认使用 Accept-Language 请求头自动解析 Locale。可自定义 LocaleResolver(如基于 URL 参数或 Cookie)。
配置文件编码必须为 UTF-8(Spring Boot 默认支持)。在 IDE 中确保 .properties 文件保存为 UTF-8;若含中文,无需转 Unicode(Spring Boot 2.6+ 原生支持 UTF-8 properties)。旧版 Spring 需手动配置 encoding,但 Spring Boot 已内置处理。
多参数消息消息中可包含占位符 {0}, {1}见下方代码示例 2占位符从 0 开始计数,按顺序替换。
刷新缓存(开发)修改 messages 文件后需重启或配置 reloadapplication.propertiesspring.messages.cache-duration=0仅用于开发环境;生产环境应启用缓存提升性能。

代码示例 1:消息资源文件

# messages.properties(默认)
greeting=Hello

# messages_zh_CN.properties
greeting=你好

代码示例 2:多参数消息

# messages.properties
welcome.message=Welcome, {0}! You have {1} unread messages.

5.2 使用 #messages 工具对象

方法/表达式语法示例用途代码示例注意事项
获取简单消息#{'greeting'}根据 key 获取当前 Locale 对应的消息文本。<p th:text="#{greeting}"></p>若 key 不存在,会抛出异常;建议提供默认 fallback。
获取带参数的消息#{'welcome.message', user.name, count}替换消息中的占位符 {0}, {1}<p th:text="#{welcome.message($ {currentUser.name}, $ {unreadCount})}"></p>参数顺序必须与消息定义一致。
安静模式(不抛异常)#{'unknown.key'}(配合默认值)避免因缺失 key 导致页面渲染失败。<p th:text="$ {#messages.msgOrNull('optional.hint') ?: 'Default Hint'}"></p>#messages.msgOrNull() 返回 null 而非抛异常。
#messages.msg()$ {#messages.msg('key')}在非 th:text 属性中获取消息(如 th:placeholder)。<input type="text" th:placeholder="$ {#messages.msg('search.placeholder')}" />等价于 #{'key'},但可在任意表达式中使用。
#messages.msgOrNull()$ {#messages.msgOrNull('maybe.missing')}安全获取消息,不存在时返回 null。见下方代码示例推荐用于可选内容。
消息 key 动态拼接#{'error.code.' + errorCode}根据变量动态构造 key。<span th:text="#{'error.code.' + $ {errorCode}}"></span>确保拼接后的 key 存在于资源文件中。

代码示例:#messages.msgOrNull() 使用

<div th:if="$ {#messages.msgOrNull('promo.banner') != null}"
     th:text="#{promo.banner}"></div>

5.3 动态切换语言实现

步骤名称操作细节代码示例注意事项
配置 LocaleResolver定义基于 Cookie 或 Session 的 LocaleResolver见下方代码示例 1Cookie 方案可跨会话保持语言;Session 方案仅当前会话有效。
创建语言切换控制器提供接口接收 lang 参数并设置 Locale。见下方代码示例 2使用 RequestContextUtils 获取已注册的 LocaleResolver
前端语言切换链接提供多语言切换按钮。见下方代码示例 3href 路径需与控制器映射一致。
自动重定向回原页面通过 Referer 或存储 returnUrl 实现无缝切换。(见上方控制器中 redirect: + request.getHeader("Referer")Referer 可能为空,需加空值判断。
浏览器默认语言适配未显式设置时,按 Accept-Language 自动匹配。—(Spring Boot 默认行为)需在 messages 文件中覆盖常用语言(如 en, zh_CN)。
前端存储用户偏好使用 JavaScript 读取 Cookie 并高亮当前语言。见下方代码示例 4仅用于 UI 反馈,实际语言由服务端控制。

代码示例 1:配置 LocaleResolver

@Configuration
public class LocaleConfig {
    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver resolver = new CookieLocaleResolver();
        resolver.setCookieName("LANG");
        resolver.setDefaultLocale(Locale.ENGLISH);
        return resolver;
    }
}

代码示例 2:语言切换控制器

@Controller
public class LanguageController {
    @GetMapping("/lang/{lang}")
    public String changeLang(@PathVariable String lang,
                             HttpServletRequest request,
                             HttpServletResponse response) {
        LocaleResolver resolver = RequestContextUtils.getLocaleResolver(request);
        resolver.setLocale(request, response, StringUtils.parseLocaleString(lang));
        return "redirect:" + request.getHeader("Referer");
    }
}

代码示例 3:前端语言切换链接

<a href="/lang/en">English</a>
<a href="/lang/zh_CN">中文</a>

代码示例 4:前端读取 Cookie 高亮当前语言

<script>
    const lang = document.cookie.split('; ')
        .find(row => row.startsWith('LANG='))
        ?.split('=')[1];
    if (lang) document.querySelector(`[href='/lang/${lang}']`)
        .classList.add('active');
</script>

第六章:工具对象与内置功能

6.1 #dates#calendars#numbers#strings 等工具对象

工具对象方法/表达式用途代码示例注意事项
#dates${#dates.format(date, 'yyyy-MM-dd')}格式化 java.util.Date 对象<span th:text="${#dates.format(user.birthDate, 'yyyy年MM月dd日')}"></span>模式字符串遵循 SimpleDateFormat 规范
${#dates.createNow()}获取当前时间(java.util.Date<p>Now: <span th:text="${#dates.format(#dates.createNow(), 'HH:mm:ss')}"></span></p>等价于 new Date(),但可在模板中直接调用
${#dates.day(date)}提取日期中的”日”部分(1-31)<span th:text="${#dates.day(orderDate)}"></span>返回 int 类型
#calendars${#calendars.format(calendar, 'yyyy/MM/dd')}格式化 java.util.Calendar 对象<time th:text="${#calendars.format(eventCalendar, 'EEEE, MMMM dd')}"></time>用法与 #dates 类似,适用于 Calendar 类型
${#calendars.monthName(calendar)}获取月份全名(如 “January”)<span th:text="${#calendars.monthName(currentCal)}"></span>依赖当前 Locale
#numbers${#numbers.formatDecimal(number, minInt, maxFrac)}格式化数字(整数位、小数位)<span th:text="${#numbers.formatDecimal(price, 1, 2)}"></span> → 显示 “123.45”minInt=最小整数位,maxFrac=最大小数位;不足补0
${#numbers.formatCurrency(amount)}按 Locale 格式化货币<span th:text="${#numbers.formatCurrency(total)}"></span> → 如 “$1,234.56”自动应用本地货币符号和千分位
${#numbers.sequence(from, to)}生成数字序列(用于循环)<select><option th:each="i : ${#numbers.sequence(1,10)}" th:text="${i}"></option></select>生成 [from, to] 的整数列表
#strings${#strings.toUpperCase(str)}转大写<span th:text="${#strings.toUpperCase(username)}"></span>支持 null 安全(null 输入返回 null)
${#strings.abbreviate(str, width)}截断字符串并加 ”…”<p th:text="${#strings.abbreviate(description, 50)}"></p>若长度 ≤ width 则原样返回
${#strings.defaultString(str, 'N/A')}提供默认值(当 str 为 null 或空)<span th:text="${#strings.defaultString(email, '未填写')}"></span>空字符串("")也被视为需替换
${#strings.contains(str, 'keyword')}判断是否包含子串<div th:if="${#strings.contains(comment, 'spam')}">Flagged</div>区分大小写
#lists / #sets${#lists.isEmpty(list)}判断 List 是否为空<div th:if="${#lists.isEmpty(items)}">No items</div>推荐用于条件判断,比直接判 null 更安全
${#lists.size(list)}获取 List 长度<span th:text="${'Total: ' + #lists.size(products)}"></span>等价于 list.size(),但支持 null(返回 0)

6.2 URL 处理(@{}#httpServletRequest

表达式/对象语法示例用途代码示例注意事项
@{}(链接表达式)@{/user/profile}生成相对于应用上下文的绝对路径<a th:href="@{/dashboard}">Dashboard</a> → 渲染为 <a href="/myapp/dashboard">自动处理 server.servlet.context-path
@{/order/{id}(id=${orderId})}路径变量替换<a th:href="@{/product/{pid}(pid=${product.id})}">View</a>支持多个路径变量
@{/search(q=${query}, page=2)}添加查询参数<a th:href="@{/api/data(format='json', token=${authToken})}">API</a>参数自动 URL 编码
@{https://example.com}绝对 URL(不加 context-path)<img th:src="@{https://cdn.example.com/logo.png}">http(s):// 开头时视为外部资源
#httpServletRequest${#httpServletRequest.requestURI}获取当前请求 URI<input type="hidden" name="redirect" th:value="${#httpServletRequest.requestURI}">可用于登录后跳转回原页面
${#httpServletRequest.remoteAddr}获取客户端 IP<p>IP: <span th:text="${#httpServletRequest.remoteAddr}"></span></p>注意代理环境下可能需读 X-Forwarded-For
${#httpServletRequest.getParameter('lang')}获取请求参数<span th:if="${#httpServletRequest.getParameter('debug') == 'true'}">Debug Mode</span>不推荐在模板中直接读参,应由 Controller 处理
相对路径 vs 绝对路径@{profile}(无前导 /相对于当前路径若当前页为 /user/settings,则 @{profile}/user/profile建议始终使用 @{/xxx} 避免歧义
静态资源处理@{/css/style.css}引用静态资源(自动加版本戳,若启用)<link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}">Spring Boot 默认映射 /static/public/resources/META-INF/resources

6.3 安全表达式(Spring Security 集成)

安全表达式语法示例用途代码示例注意事项
sec:authorize<div sec:authorize="hasRole('ADMIN')">根据权限控制元素渲染(需引入 security dialect)<button sec:authorize="hasAuthority('USER_DELETE')">Delete</button>必须添加 Thymeleaf Spring Security 依赖
#authentication${#authentication.name}获取当前认证用户名<span>Welcome, <b th:text="${#authentication.name}"></b>!</span>若未登录,#authentication 为 null
${#authentication.principal.email}访问 UserDetails 自定义属性<p>Email: <span th:text="${#authentication?.principal?.email}"></span></p>使用 ?. 防止 NPE;需自定义 UserDetailsService 返回含 email 的对象
${#authentication.authorities}获取权限集合<div th:each="auth : ${#authentication.authorities}" th:text="${auth.authority}"></div>authorities 是 GrantedAuthority 列表
内联权限判断th:if="${#authorization.expression('hasRole(''ADMIN'')')}"在标准方言中使用安全表达式<div th:if="${#authorization.expression('isAuthenticated()')}">Logout</div>需启用 #authorization 工具对象(Spring Security 集成后自动可用)
常用表达式isAuthenticated()判断是否已认证<a th:if="${#authorization.expression('!isAuthenticated()')}" th:href="@{/login}">Login</a>其他:isAnonymous(), isRememberMe(), hasAnyRole('A','B')
依赖配置Maven 依赖启用 sec:* 属性支持见下方代码示例 1Spring Boot 3.x 需用 springsecurity6;2.x 用 springsecurity5
命名空间声明xmlns:sec="http://www.thymeleaf.org/extras/spring-security"在 HTML 中启用 sec 前缀见下方代码示例 2仅用于 IDE 提示,运行时非必需

代码示例 1:Maven 依赖

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

代码示例 2:命名空间声明

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

第七章:表单处理与数据绑定

7.1 th:object*{} 表达式

概念/属性语法示例用途代码示例注意事项
th:objectth:object="$ {userForm}"将一个对象绑定为当前表单的作用域对象,后续可使用 *{} 简化属性访问。见下方代码示例 1绑定对象必须存在于 Model 中;通常为 DTO 或实体类实例。
*{} 表达式*{email}th:object 作用域内访问对象属性,等价于 $ {user.email}<p>Current email: <span th:text="*{email}"></span></p>仅在 th:object 子元素中有效;脱离作用域将报错。
嵌套属性访问*{address.city}访问对象的嵌套属性(需确保中间对象非 null)。<input type="text" th:field="*{profile.phoneNumber}" />若 profile 为 null,会抛出 PropertyAccessException;建议在后端初始化嵌套对象。
$ {} 混用th:value="$ {prefix} + *{suffix}"在同一表达式中混合使用全局变量和绑定对象属性。<input type="hidden" th:value="'ID_' + *{id}" />*{} 仍受限于 th:object 作用域。
作用域限制*{} 不能用于 th:object 外部防止误用导致解析失败。见下方代码示例 2确保 *{} 位于 th:object 标签内部(包括子元素)。

代码示例 1:th:object*{}

<form th:object="$ {user}" action="#" method="post">
    <input type="text" th:value="*{name}" />
</form>

代码示例 2:作用域限制(错误示例)

<div th:object="$ {user}"></div>
<p th:text="*{name}"></p>  <!-- 脱离作用域 -->

7.2 表单字段绑定(th:field

属性/用法语法示例用途代码示例注意事项
th:fieldth:field="*{username}"自动绑定表单字段到对象属性,自动设置 name、value、type 等属性。<input type="text" th:field="*{username}" /> → 渲染为 <input type="text" name="username" value="john" />推荐用于 Spring MVC 表单绑定;比 th:value 更强大。
自动类型转换th:field="*{birthDate}"(Date 类型)支持日期、布尔、数字等类型的自动格式化与回填。<input type="date" th:field="*{birthDate}" />日期格式需与 Spring 的 @DateTimeFormat 一致。
复选框(checkbox)th:field="*{newsletter}"自动处理布尔值:checked 状态与 value 同步。<input type="checkbox" th:field="*{newsletter}" />不要手动设置 value="true";Thymeleaf 自动处理。若 newsletter=true,则渲染 checked="checked"
多选复选框th:field="*{roles}"绑定 List 或数组,自动匹配选中项。见下方代码示例 1roles 需为 Collection 类型;value 必须显式指定。
单选按钮(radio)th:field="*{gender}"根据字段值自动选中对应 radio。见下方代码示例 2所有 radio 共享同一 th:field 表达式。
下拉选择(select)th:field="*{country}"自动选中 option;需配合 th:each 遍历选项。见下方代码示例 3option 的 value 必须与绑定属性值类型一致(如 String vs Enum)。
禁用手动设置 name/value使用 th:field 后,不应再设置 name、value、checked 等属性。见下方代码示例 4Thymeleaf 会覆盖手动属性,导致绑定失效。

代码示例 1:多选复选框

<input type="checkbox" th:field="*{roles}" value="ADMIN" />
<input type="checkbox" th:field="*{roles}" value="USER" />

代码示例 2:单选按钮

<input type="radio" th:field="*{gender}" value="M" /> Male
<input type="radio" th:field="*{gender}" value="F" /> Female

代码示例 3:下拉选择

<select th:field="*{country}">
    <option th:each="c : ${countries}"
            th:value="${c.code}"
            th:text="${c.name}"></option>
</select>

代码示例 4:错误示例(禁用手动设置)

<!-- 错误:手动设置 name 会导致绑定失效 -->
<input type="text" th:field="*{name}" name="customName" />

7.3 错误信息展示(#fields.hasErrorsth:errors

方法/属性语法示例用途代码示例注意事项
th:errorsth:errors="*{email}"显示指定字段的所有验证错误信息(来自 JSR-303 / Spring Validation)。<div th:errors="*{email}" class="error"></div>自动遍历 BindingResult 中该字段的错误;若无错误则不渲染元素。
#fields.hasErrors()$ {#fields.hasErrors('email')}判断某字段是否有错误,常用于条件样式。见下方代码示例 1参数为字段名字符串(非 *{email} 表达式)。
#fields.errors()$ {#fields.errors('password')}获取某字段的错误信息列表(List)。见下方代码示例 2可用于自定义错误展示结构。
全局错误th:errors="*"显示对象级别的全局错误(非字段绑定错误)。<div th:errors="*" class="alert alert-danger"></div>通常由 Validator 抛出 ObjectError。
字段名转义字段名支持嵌套(如 "address.city")。<span th:if="$ {#fields.hasErrors('address.city')}">Invalid city</span>th:field 中的路径一致。
国际化错误消息messages.properties 中定义错误消息可本地化。见下方代码示例 3占位符 {0}=字段名, {1}=max, {2}=min(依注解而定)。
自定义 CSS 类结合 th:classappend动态添加错误样式类。见下方代码示例 4避免覆盖原有 class,使用 classappend 而非 class

代码示例 1:#fields.hasErrors() 条件样式

<input type="email"
       th:field="*{email}"
       th:class="$ {#fields.hasErrors('email')} ? 'form-control is-invalid' : 'form-control'" />

代码示例 2:#fields.errors() 自定义错误展示

<ul th:if="$ {#fields.hasErrors('password')}">
    <li th:each="err : $ {#fields.errors('password')}" th:text="$ {err}"></li>
</ul>

代码示例 3:国际化错误消息

# messages_zh_CN.properties
Size.user.password=密码长度必须在 {2} 到 {1} 位之间

代码示例 4:th:classappend 动态添加错误样式

<input type="text"
       th:field="*{phone}"
       th:classappend="$ {#fields.hasErrors('phone')} ? 'is-invalid'" />

第八章:高级特性与性能优化

8.1 模板缓存机制

配置项 / 概念说明代码示例 / 配置方式注意事项
默认缓存行为Thymeleaf 在生产模式下默认启用模板缓存,提升渲染性能。Spring Boot 应用中无需额外配置;devtools 启用时自动禁用缓存。开发环境建议关闭缓存以便实时预览修改。
缓存控制配置通过 application.properties 控制缓存开关与有效期。见下方代码示例cache-ttl 仅在 Thymeleaf 3.0.12+ 支持;旧版需自定义 TemplateResolver。
禁用缓存(开发)便于调试模板变更。application.propertiesspring.thymeleaf.cache=false或通过启动参数:--spring.thymeleaf.cache=false
手动清除缓存在运行时强制刷新模板(适用于动态模板场景)。见下方代码示例仅在确实需要动态更新模板时使用;频繁调用影响性能。
缓存粒度按模板名称缓存解析后的模板结构(非 HTML 字符串)。多个请求渲染同一模板共享缓存;不同模板独立缓存。
生产环境建议始终启用缓存,避免重复解析模板文件。若使用外部存储(如数据库存模板),需自行实现缓存失效逻辑。

代码示例:缓存控制与手动清除

# application.properties
spring.thymeleaf.cache=true
spring.thymeleaf.cache-ttl=3600000  # 1小时(毫秒)
@Autowired
private TemplateEngine templateEngine;

public void clearCache() {
    templateEngine.getConfiguration().getTemplateManager().clearCaches();
}

8.2 自定义方言(Dialect)开发

步骤名称操作细节代码示例注意事项
创建方言类继承 AbstractProcessorDialect 或实现 IDialect 接口。见下方代码示例 1方言前缀(如 “my”)用于 th:* 替代(如 my:text)。
实现自定义处理器继承 AbstractAttributeTagProcessor,重写 doProcess 方法。见下方代码示例 2processor 名称即为属性名(如 “highlight” → my:highlight)。
注册方言在 Spring Boot 中声明为 @Bean见下方代码示例 3Thymeleaf 自动发现所有 IDialect 类型的 Bean。
使用自定义属性在模板中使用新方言前缀。<p my:highlight="true">This text will be highlighted.</p>需在 HTML 标签中声明命名空间(可选,仅 IDE 提示用):xmlns:my="http://example.com/mydialect"
表达式对象扩展通过 IExpressionObjectDialect 添加全局工具对象(如 #myUtils)。见下方代码示例 4工具对象可在表达式中调用:$ {#myUtils.doSomething()}
调试技巧日志输出处理器执行过程。doProcess 中添加日志:System.out.println("Processing tag: " + tag.getElementCompleteName());避免在生产环境打印过多日志。

代码示例 1:创建方言类

public class MyDialect extends AbstractProcessorDialect {
    public MyDialect() {
        super("MyDialect", "my", StandardDialect.PROCESSOR_PRECEDENCE);
    }

    @Override
    public Set<IProcessor> getProcessors(String dialectPrefix) {
        Set<IProcessor> processors = new HashSet<>();
        processors.add(new MyCustomAttributeTagProcessor(dialectPrefix));
        return processors;
    }
}

代码示例 2:实现自定义处理器

public class MyCustomAttributeTagProcessor extends AbstractAttributeTagProcessor {
    public MyCustomAttributeTagProcessor(String dialectPrefix) {
        super(TemplateMode.HTML, dialectPrefix, "highlight",
              true, null, false, 0);
    }

    @Override
    protected void doProcess(ITemplateContext context,
                             IProcessableElementTag tag,
                             AttributeName attributeName,
                             String attributeValue,
                             IElementTagStructureHandler structureHandler) {
        structureHandler.setAttribute("style", "background-color: yellow;");
    }
}

代码示例 3:注册方言

@Bean
public MyDialect myDialect() {
    return new MyDialect();
}

代码示例 4:表达式对象扩展

public class MyExpressionObjectDialect extends AbstractExpressionObjectDialect {
    @Override
    public Map<String, Object> getExpressionObjects(ITemplateContext context) {
        Map<String, Object> objects = new HashMap<>();
        objects.put("myUtils", new MyUtilityClass());
        return objects;
    }
}

8.3 模板预处理与后处理

处理阶段机制 / 接口用途代码示例注意事项
预处理ITemplatePreProcessor在模板解析前修改原始模板文本(如替换占位符、注入全局变量)。见下方代码示例 1返回修改后的字符串;影响后续所有解析步骤。
后处理ITemplatePostProcessor在模板渲染完成后修改最终 HTML 输出(如压缩、添加统计代码)。见下方代码示例 2适用于 SEO 优化、安全头注入等场景。
注册预/后处理器通过 TemplateEngine 配置将自定义处理器加入 Thymeleaf 引擎。见下方代码示例 3多个处理器按添加顺序执行。
使用场景示例预处理:多租户模板定制动态适配不同客户 UI;增强安全策略。预处理:替换 {{TENANT_LOGO}} 为实际 URL预处理操作需高效,避免正则回溯爆炸。
后处理:添加 CSP nonce后处理:在 <script> 标签插入 nonce="$ {nonce}"
性能影响预/后处理在每次渲染时执行可能成为性能瓶颈。仅在必要时使用;复杂逻辑建议移至 Controller 或 View 层。
与缓存关系预处理在缓存前执行,后处理在缓存后执行预处理结果可被缓存,后处理结果不可缓存。若模板经预处理后固定,则缓存有效;后处理每次生成新 HTML,无法缓存。动态后处理(如含时间戳)会降低缓存命中率。

代码示例 1:预处理(去除注释)

public class CommentStripperPreProcessor implements ITemplatePreProcessor {
    @Override
    public String process(ITemplateContext context, String templateContent) {
        return templateContent.replaceAll("<!--.*?-->", "");
    }
}

代码示例 2:后处理(HTML 压缩)

public class HtmlMinifierPostProcessor implements ITemplatePostProcessor {
    @Override
    public String process(ITemplateContext context, String renderedHtml) {
        return renderedHtml.replaceAll("\\s+", " ");
    }
}

代码示例 3:注册预/后处理器

@Bean
public TemplateEngine templateEngine(SpringStandardDialect springDialect) {
    SpringTemplateEngine engine = new SpringTemplateEngine();
    engine.setTemplateResolvers(Arrays.asList(templateResolver()));
    engine.addDialect(springDialect);
    engine.addTemplatePreProcessor(new MyPreProcessor());
    engine.addTemplatePostProcessor(new MyPostProcessor());
    return engine;
}

第九章:Thymeleaf 与 Spring 生态集成

9.1 Spring MVC 数据模型传递

机制 / 注解语法示例用途代码示例注意事项
@Controller + Modelpublic String home(Model model)将数据添加到模板上下文,供 Thymeleaf 渲染使用。见下方代码示例 1返回值为模板路径(不含 .html 后缀);自动解析为 templates/user/list.html
@ModelAttribute@ModelAttribute("currentUser")全局或方法级预加载公共数据(如用户信息、菜单)。见下方代码示例 2所有控制器方法均可访问 currentUser 变量。
RedirectAttributesredirectAttributes.addFlashAttribute(...)重定向时传递一次性消息(如成功提示)。见下方代码示例 3Flash 属性仅在下一次请求中可用,适合 POST-REDIRECT-GET 模式。
直接返回对象(@ResponseBody 冲突)❌ 不适用Thymeleaf 视图需返回 String 或 ModelAndView,不能返回 JSON 对象。见下方代码示例 4若需同时支持 HTML 和 JSON,应使用内容协商或分开接口。
ModelAndViewreturn new ModelAndView("view", "data", obj);显式构建视图与模型(较少用,Model 更简洁)。return new ModelAndView("product/detail", "product", productService.findById(id));功能等价于 model.addAttribute + return "view"
模板变量命名规范使用语义化名称(如 userList 而非 list提高模板可读性与维护性。model.addAttribute("orderSummary", summary);避免使用 Java 关键字或 Thymeleaf 保留词(如 objectfield)。

代码示例 1:@Controller + Model

@Controller
public class UserController {
    @GetMapping("/users")
    public String listUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        model.addAttribute("title", "User List");
        return "user/list";
    }
}

代码示例 2:@ModelAttribute 全局预加载

@ControllerAdvice
public class GlobalModel {
    @ModelAttribute("currentUser")
    public User getCurrentUser(Authentication auth) {
        return auth != null ? (User) auth.getPrincipal() : null;
    }
}

代码示例 3:RedirectAttributes Flash 消息

@PostMapping("/save")
public String save(User user, RedirectAttributes ra) {
    userService.save(user);
    ra.addFlashAttribute("message", "Saved successfully!");
    return "redirect:/users";
}

代码示例 4:错误示例(返回对象而非视图)

// 错误:会返回 JSON 而非 HTML
@GetMapping("/page")
public List<User> getData() { ... }

9.2 Spring Security 权限控制标签

安全表达式 / 标签属性语法示例用途代码示例注意事项
sec:authorize<div sec:authorize="hasRole('ADMIN')">根据权限表达式决定是否渲染元素(需引入 security dialect)。<button sec:authorize="hasRole('ADMIN')">Delete</button>必须添加 thymeleaf-extras-springsecurity 依赖。
sec:authentication<span sec:authentication="name"></span>直接输出认证对象的属性(如用户名、权限)。<span sec:authentication="name">Hello!</span>等价于 ${#authentication.name},但更简洁。
hasRole('ROLE')表达式:hasRole('ADMIN')判断用户是否拥有指定角色(自动加 ROLE_ 前缀)。<div sec:authorize="hasRole('ADMIN')">Reports</div>若数据库角色存为 “ROLE_ADMIN”,则表达式写 hasRole('ADMIN')
hasAuthority('AUTH')表达式:hasAuthority('ORDER_CREATE')判断是否拥有指定权限(不加前缀,精确匹配)。<div sec:authorize="hasAuthority('ORDER_CREATE')">Create Order</div>推荐用于细粒度权限控制。
isAuthenticated()表达式:isAuthenticated()判断用户是否已通过认证(非匿名)。<a sec:authorize="isAuthenticated()">Logout</a>匿名用户(未登录)返回 false。
isAnonymous()表达式:isAnonymous()判断是否为匿名用户。<a sec:authorize="isAnonymous()">Login</a>!isAuthenticated() 等价。
hasAnyRole(...)表达式:hasAnyRole('USER','ADMIN')满足任一角色即授权。<div sec:authorize="hasAnyRole('USER','ADMIN')">Content Editor</div>参数为字符串列表,自动加 ROLE_ 前缀。
依赖配置Maven 依赖启用 sec:* 标签支持。见下方代码示例 1Spring Boot 3.x 使用 springsecurity6;2.x 用 springsecurity5。
命名空间声明xmlns:sec="..."在 HTML 中启用 sec 前缀(IDE 友好)。见下方代码示例 2运行时非必需,但建议保留以获 IDE 支持。

代码示例 1:Maven 依赖

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

代码示例 2:命名空间声明

<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

9.3 与 WebFlux 的兼容性(Reactive 支持)

特性 / 限制说明代码示例 / 配置方式注意事项
Thymeleaf 官方支持自 3.0.8 起支持 Spring WebFlux(反应式渲染)。见下方代码示例 1无需额外依赖;Thymeleaf 自动适配反应式上下文。
控制器返回类型使用 Mono<String>Flux<?> 传递模型数据。见下方代码示例 2模型填充必须在返回 Mono 之前完成(通过 doOnNext 等操作符)。
模板引擎自动配置Spring Boot 自动配置 ThymeleafReactiveViewResolver无需手动配置 ViewResolver。
不支持的特性th:include(已废弃)、部分同步工具对象(如 #httpServletRequest❌ 无法在模板中使用 ${#httpServletRequest}反应式环境下无 HttpServletRequest,应使用 ServerHttpRequest
替代方案使用 WebSession 代替 HttpSession见下方代码示例 3WebSession 是反应式会话对象。
性能考量模板渲染仍为阻塞操作,但由专用线程池处理高并发场景下需监控线程池;避免在模板中执行耗时逻辑。
流式渲染(实验性)Thymeleaf 3.1+ 支持 I/O 流式输出(需手动配置)需自定义 ReactiveTemplateEngine 并启用流式模式目前 Spring Boot 未默认启用;适用于超大页面分块传输。

代码示例 1:WebFlux 依赖(Gradle)

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

代码示例 2:Reactive 控制器

@Controller
public class ReactiveController {
    @GetMapping("/reactive")
    public Mono<String> view(Model model) {
        return userService.findAll()
            .collectList()
            .doOnNext(users -> model.addAttribute("users", users))
            .thenReturn("reactive/list");
    }
}

代码示例 3:WebSession 替代方案

@GetMapping("/cart")
public Mono<String> cart(WebSession session, Model model) {
    model.addAttribute("items", session.getAttribute("cart"));
    return Mono.just("cart/view");
}

第十章:调试、测试与部署

10.1 模板语法错误排查

步骤名称操作细节代码示例 / 日志片段注意事项
查看控制台错误日志Thymeleaf 在解析失败时会抛出 TemplateProcessingExceptionorg.thymeleaf.exceptions.TemplateInputException: Error resolving template "user/profile", template might not exist or might not be accessible.首先确认模板路径是否正确(相对于 templates/ 目录)。
检查属性拼写常见错误:th:tex → 应为 th:text*{emial} → 应为 *{email}错误日志:org.thymeleaf.exceptions.PropertyAccessException: Could not access property "emial" of context objectIDE 插件(如 Thymeleaf Live Templates)可辅助提示。
验证表达式合法性确保 ${}*{}@{} 表达式括号匹配、引号闭合。错误示例:<span th:text="$ {user.name'}"></span> ← 单双引号混用字符串字面量建议统一用单引号:'static text'
检查对象是否为 null若绑定对象未传入 Model,*{} 表达式会报错。控制器未调用 model.addAttribute("user", ...),但模板使用 th:object="$ {user}"使用 th:if="$ {user != null}" 包裹或提供默认值。
启用详细日志配置 logging.level.org.thymeleaf=DEBUG见下方代码示例开发阶段开启,便于定位表达式求值过程。
模板编码问题文件保存为非 UTF-8 导致中文乱码或解析失败。消息文件含中文但未保存为 UTF-8(旧版需转 Unicode)Spring Boot 2.6+ 原生支持 UTF-8 .properties,但仍需 IDE 正确设置。
标签未闭合或嵌套错误HTML 结构不合法(如 <p> 内嵌 <div>)可能被浏览器修正,但 Thymeleaf 严格解析。<p><div>...</div></p> → 解析异常使用 W3C 验证器检查 HTML 合法性。

代码示例:启用详细日志

# application.properties
logging.level.org.thymeleaf.templateparser=TRACE
logging.level.org.thymeleaf.expressions=DEBUG

10.2 单元测试模板渲染结果

测试方法操作细节代码示例注意事项
使用 SpringBootTest启动完整上下文,模拟请求并断言响应内容。见下方代码示例 1适用于端到端测试;启动较慢。
模拟 TemplateEngine 渲染直接调用 TemplateEngine 渲染字符串,无需 HTTP 层。见下方代码示例 2需注入 TemplateEngine;适合测试片段模板。
验证模型数据传递使用 MockMvc 检查 Controller 是否正确添加属性。见下方代码示例 3不验证 HTML,仅验证 Model 内容。
测试安全标签渲染结合 @WithMockUser 模拟认证用户,验证 sec:authorize 行为。见下方代码示例 4需启用 Spring Security 测试支持。
断言特定元素存在使用 HtmlUnit 或 JSoup 解析 HTML 并查询节点。见下方代码示例 5比字符串 contains 更精准,避免误匹配。
覆盖错误消息测试模拟表单提交错误,验证 th:errors 是否渲染。见下方代码示例 6需配置 BindingResult 和 Validator。

代码示例 1:@SpringBootTest 端到端测试

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase
class UserControllerTest {
    @LocalServerPort int port;

    @Test
    void testUserListPage() {
        String body = RestAssured.given().port(port)
            .when().get("/users")
            .then().statusCode(200)
            .extract().asString();
        assertThat(body).contains("User Management");
    }
}

代码示例 2:直接调用 TemplateEngine 渲染

@Test
void testTemplateRendersCorrectly() {
    Context context = new Context();
    context.setVariable("message", "Hello Test");
    String result = templateEngine.process("fragments/alert", context);
    assertThat(result).contains("Hello Test");
}

代码示例 3:MockMvc 验证模型数据

@Test
void shouldAddUsersToModel() throws Exception {
    mockMvc.perform(get("/users"))
        .andExpect(model().attributeExists("users"))
        .andExpect(model().attribute("users", hasSize(2)));
}

代码示例 4:@WithMockUser 测试安全标签

@Test
@WithMockUser(roles = "ADMIN")
void adminButtonVisible() throws Exception {
    mockMvc.perform(get("/dashboard"))
        .andExpect(content().string(containsString("Delete User")));
}

代码示例 5:JSoup 解析 HTML 断言元素

Document doc = Jsoup.parse(renderedHtml);
Element emailInput = doc.selectFirst("input[name=email]");
assertThat(emailInput.attr("value")).isEqualTo("test@example.com");

代码示例 6:表单错误消息测试

mockMvc.perform(post("/register")
        .param("email", ""))
    .andExpect(status().isOk())
    .andExpect(content().string(containsString("邮箱不能为空")));

10.3 生产环境配置建议

配置项推荐值 / 方式说明注意事项
模板缓存spring.thymeleaf.cache=true提升性能,避免重复解析模板。必须启用;开发环境可设为 false。
模板模式spring.thymeleaf.mode=HTML确保按 HTML5 规范解析(而非 XML、LEGACYHTML5 等)。默认即为 HTML,通常无需显式设置。
缓存 TTL(可选)spring.thymeleaf.cache-ttl=3600000(1小时)定期刷新缓存(Thymeleaf 3.0.12+ 支持)。适用于模板可能动态更新的场景(如 CMS)。
禁用模板解析日志logging.level.org.thymeleaf=INFO避免输出大量 DEBUG/TRACE 日志影响性能。生产环境应限制日志级别。
静态资源缓存配置 Cache-Control 头(由 Spring ResourceHandler 处理)spring.web.resources.cache.cachecontrol.max-age=3600Thymeleaf 模板本身不处理静态资源缓存。
模板位置默认 classpath:/templates/不建议修改;若需外部化(如热部署),可配置 spring.thymeleaf.prefix=file:/opt/app/templates/外部模板需确保权限与安全访问控制。
XSS 防护默认自动转义(如 th:text用户输入通过 th:text 输出时自动 HTML 转义。若使用 th:utext(不转义),必须手动过滤(如 OWASP Java Encoder)。
错误页面定制实现 /error 模板(如 error.htmlSpring Boot 自动渲染 templates/error.html可通过 @ControllerAdvice 全局异常处理补充模型数据。
内存与线程池监控 TemplateEngine 的缓存内存占用Thymeleaf 缓存模板结构,大项目可能占用数百 MB 内存。高并发下确保 JVM 堆内存充足。
安全头集成结合 Spring Security 添加 CSP、X-Content-Type-Options 等配置 SecurityFilterChain 添加 headers()Thymeleaf 本身不处理 HTTP 头,需框架层支持。

第十一章:代码生成

11.1 配置依赖

依赖名称语法(Maven)用途代码示例注意事项
Thymeleaf见下方代码示例 1提供模板引擎核心功能,用于渲染代码模板见下方代码示例 1若用于 Spring Boot 项目,建议使用 spring-boot-starter-thymeleaf
Spring Boot Starter Thymeleaf见下方代码示例 2自动配置 Thymeleaf,集成 Spring MVC见下方代码示例 2默认模板路径为 src/main/resources/templates/,生成代码时需覆盖或自定义模板解析器。
MyBatis-Plus Generator见下方代码示例 3提供代码生成器,可配合 Thymeleaf 模板见下方代码示例 3需额外引入 Velocity 或 Freemarker/Thymeleaf 作为模板引擎。
Thymeleaf Extras Java8Time见下方代码示例 4支持在模板中使用 Java 8 时间格式<span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd')}">仅在模板中需要处理日期时间时使用。

代码示例 1:Thymeleaf 核心依赖

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>

代码示例 2:Spring Boot Starter Thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

代码示例 3:MyBatis-Plus Generator

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.5</version>
</dependency>

代码示例 4:Thymeleaf Extras Java8Time

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.1.0</version>
</dependency>

11.2 生成后端代码

方法/组件语法用途代码示例注意事项
TemplateEngine 初始化TemplateEngine templateEngine = new TemplateEngine();创建并配置 Thymeleaf 引擎用于代码生成见下方代码示例 1必须设置 TemplateModeTEXT,否则会转义 HTML 特殊字符。
渲染模板生成 Java 类String result = templateEngine.process("EntityTemplate", context);使用上下文数据渲染模板,输出 Java 代码字符串见下方代码示例 2模板文件建议命名为 .java.thymeleaf,避免 IDE 误识别。
写入文件Files.write(Paths.get(...), javaCode.getBytes(StandardCharsets.UTF_8));将生成的代码写入磁盘见下方代码示例 3需提前创建目录,注意文件编码统一为 UTF-8。

代码示例 1:TemplateEngine 初始化

TemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("templates/code/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.TEXT);

TemplateEngine templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(resolver);

代码示例 2:渲染模板生成 Java 类

Context context = new Context();
context.setVariable("entityName", "User");
context.setVariable("fields", fieldList);
String javaCode = templateEngine.process("Entity.java.thymeleaf", context);

代码示例 3:写入文件

Files.write(Paths.get("src/main/java/com/example/entity/User.java"),
    javaCode.getBytes(StandardCharsets.UTF_8));

11.3 生成前端代码

方法/组件语法用途代码示例注意事项
Thymeleaf 模板渲染 HTMLtemplateEngine.process("user-form.html", context)生成包含表单、列表等结构的 HTML 页面见下方代码示例模板中可使用 Thymeleaf 表达式如 th:eachth:text
静态资源引用处理<link th:href="@{/css/style.css}" rel="stylesheet" />在生成的 HTML 中正确引用静态资源若用于离线生成(非 Web 环境),需禁用链接重写或替换为绝对路径。
前端模板变量注入ctx.setVariable("apiBaseUrl", "/api/v1");动态注入 API 地址、标题等配置避免硬编码,提升模板复用性。

代码示例:生成前端 HTML

Context ctx = new Context();
ctx.setVariable("columns", Arrays.asList("name", "email"));
String html = templateEngine.process("list-page.thymeleaf", ctx);

11.4 生成 SQL 脚本

方法/组件语法用途代码示例注意事项
SQL 模板定义CREATE TABLE [[${tableName}]] (...);使用 Thymeleaf 的 [[...]] 输出未转义文本见下方代码示例 1必须使用 TemplateMode.TEXT,否则括号会被转义。
渲染 SQL 文件templateEngine.process("create-table.sql.thymeleaf", context)生成建表、索引、初始化数据等 SQL 脚本同 11.2 示例注意字段类型映射(如 Java String → VARCHAR(255))。
批量生成多表脚本循环调用 process 方法,传入不同表上下文一次性生成多个表的 DDLfor (Table t : tables) { ... }可结合数据库元数据自动提取表结构。

代码示例 1:SQL 模板定义

CREATE TABLE [[${table.name}]] (
    id BIGINT PRIMARY KEY,
    [# th:each="col : ${columns}"]
    [[${col.name}]] [[${col.type}]][# th:if="${!colStat.last}"],[/th:if]
    [/th:each]
);

11.5 生成配置文件

配置类型语法示例用途代码示例注意事项
application.yml 模板见下方代码示例 1动态生成 Spring Boot 配置见下方代码示例 1使用 [[...]] 避免 YAML 缩进被破坏。
logback-spring.xml 模板<root level="[[${logLevel}]]">生成日志配置见下方代码示例 2XML 模板需设置 TemplateMode.TEXT,否则 < 会被转义为 &lt;
Dockerfile 模板FROM openjdk:17 / COPY [[${jarName}]] app.jar生成容器构建文件ctx.setVariable("jarName", "myapp.jar");注意路径和权限设置,避免运行时错误。

代码示例 1:application.yml 模板

server:
  port: [[${serverPort}]]
spring:
  datasource:
    url: [[${dbUrl}]]

代码示例 2:application.yml 渲染

Context ctx = new Context();
ctx.setVariable("serverPort", 8080);
ctx.setVariable("dbUrl", "jdbc:mysql://...");

11.6 生成 API 文档

方法/组件语法用途代码示例注意事项
OpenAPI/Swagger 模板summary: "[[${summary}]]"生成符合 OpenAPI 规范的 YAML 文档见下方代码示例 1需严格遵循 OpenAPI 结构,建议结合注解解析自动生成上下文。
Markdown API 文档模板## [[${endpoint}]] / **Method**: [[${method}]]生成人类可读的接口说明同上适合内部 Wiki 或快速文档交付。
集成 SpringDoc/OpenAPI 注解不直接使用 Thymeleaf,但可导出 JSON/YAML 后用 Thymeleaf 二次渲染自动提取 Controller 注解生成文档先通过 /v3/api-docs 获取 JSON,再作为上下文传入模板更推荐使用 Swagger UI,Thymeleaf 仅用于定制化文档导出。

代码示例 1:OpenAPI/Swagger YAML 模板

paths:
  /users:
    get:
      summary: "[[${summary}]]"

代码示例 2:OpenAPI 渲染

Context ctx = new Context();
ctx.setVariable("summary", "获取用户列表");
String openapiYaml = templateEngine.process("openapi.yaml.thymeleaf", ctx);