Thymeleaf 基础概述

什么是 Thymeleaf

Thymeleaf 是现代 Java 服务端模板引擎,支持 Web / 独立环境,可解析 HTML、XML、JS、CSS、纯文本 6 种模板模式,核心亮点是自然模板(Natural Templating):

模板本身是合法 HTML,浏览器直接打开可作为静态原型;后端渲染时才解析th:*动态属性,解决前后端协作割裂问题,完全兼容 HTML5 规范。

六大模板模式

模式

类型

用途说明

HTML

标记模式

HTML4/5/XHTML,不强制校验格式

XML

标记模式

严格要求格式良好,标签必须闭合

TEXT

文本模式

纯文本邮件、文档,无 HTML 标签解析

JAVASCRIPT

文本模式

动态 JS 文件,自带 JS 转义

CSS

文本模式

动态样式文件,CSS 语法转义

RAW

无处理模式

原样输出资源,不解析任何 Thymeleaf 语法

方言(Dialect)体系

  • 方言 = 一组标签 / 属性处理器集合,Thymeleaf 核心内置标准方言,日常开发完全够用;

  • Spring 项目使用SpringStandardDialect,将 OGNL 替换为 SpringEL,语法几乎一致;

  • 两种书写规范(完全等价):

    1. 命名空间写法(教程主推):th:text,需声明xmlns:th="http://www.thymeleaf.org"

    2. HTML5 规范写法:data-th-text,无需额外命名空间,更符合 W3C 标准

<!-- 命名空间版 -->
<p th:text="${name}">默认文本</p>
<!-- HTML5标准data版 -->
<p data-th-text="${name}">默认文本</p>

核心架构(TemplateEngine)

三大核心组件,渲染流程:模板解析器 → 模板引擎 → 上下文

ITemplateResolver 模板解析器

负责定位模板文件,内置 4 种实现:

  • WebApplicationTemplateResolver:Servlet 项目,读取/WEB-INF/templates

  • ClassLoaderTemplateResolver:读取 classpath 下模板

  • FileTemplateResolver:读取本地文件系统

  • StringTemplateResolver:直接解析字符串为模板

    常用配置:前缀 prefix、后缀 suffix、缓存开关、缓存 TTL

WebApplicationTemplateResolver resolver = new WebApplicationTemplateResolver(app);
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCacheable(true); // 生产开启缓存,开发关闭
resolver.setCacheTTLMs(3600000L); // 缓存1小时

TemplateEngine 模板引擎

核心执行类,绑定解析器、消息解析器、转换服务,调用process(模板名, 上下文, 输出流)渲染页面。

IContext 上下文

存储页面所需所有变量、区域语言;Web 专用实现WebContext,内置param/session/application快捷命名空间。

标准表达式语法(核心重点)

五大基础表达式

语法

名称

作用场景

${...}

变量表达式

获取上下文 Model、请求 / 会话数据,底层 OGNL/SpringEL

*{...}

选择表达式

配合th:object,简化对象属性取值

#{...}

消息表达式

国际化 i18n,读取.properties多语言文件

@{...}

URL 链接表达式

生成动态 URL、自动拼接上下文、参数编码

~{...}

片段表达式

引用页面公共片段(页头、页脚)

消息表达式 #{}(国际化)

  1. 消息文件规则:与模板同目录,home.properties(默认)、home_zh_CN.properties(中文)

  2. 支持占位传参:

# home.properties
home.welcome=欢迎你,{0}!
<p th:text="#{home.welcome(${session.user.name})}">欢迎</p>

变量表达式 ${}

内置 Web 快捷命名空间(无需 #):

  • ${param.xxx}:请求参数数组

  • ${session.xxx}:session 域属性

  • ${application.xxx}:Servlet 上下文属性

  • 内置基础对象(# 开头):#ctx上下文、#locale语言环境

  • 内置工具对象(附录 B 全套):#dates/#temporals/#numbers/#strings/#lists

<!-- 日期格式化 -->
<span th:text="${#calendars.format(today,'yyyy-MM-dd')}"></span>
<!-- 字符串工具 -->
<span th:text="${#strings.isEmpty(name)?'空':'有值'}"></span>

选择表达式 *{}

配合th:object绑定根对象,简化重复取值:

<div th:object="${user}">
  <p th:text="*{name}"></p> <!-- 等价 ${user.name} -->
  <p th:text="*{age}"></p>
</div>

URL 表达式 @{}

支持 4 种 URL,自动上下文路径、参数转义:

<!-- 相对上下文 -->
<a th:href="@{/product/detail(id=${prod.id})}">详情</a>
<!-- 路径变量 -->
<a th:href="@{/user/{uid}/info(uid=${user.id})}"></a>
<!-- 服务器跨上下文 -->
<a th:href="~/admin/login"></a>

片段表达式 ~{}

用于th:insert/th:replace引入公共模块,语法~{模板名::选择器}

表达式通用语法

字面量

  • 文本:'字符串' | 数字 / 布尔 /null:无需引号 true / 123 / null

  • 字面标记:纯英文单词可省略引号 th:class="main"

字符串拼接

  • 加号拼接:'你好,' + ${name}

  • 竖线简化替换(推荐):|你好,${name}|

运算与比较

算术:+ - * / %;比较:gt(>) lt(<) ge(>=) le(<=) eq(==) ne(!=)

条件表达式

  • 三目:${age>18}?'成年':'未成年'

  • Elvis 默认运算符?:(空值兜底):*{age} ?: '未知年龄'

  • 无操作符_:直接使用原型静态文本兜底

<span th:text="${user.name} ?: _">小橙同学</span>

预处理表达式 __表达式__

渲染前先替换表达式内容,动态切换逻辑;

数据转换 {{}}

${{date}}自动执行全局格式化转换服务(日期、数字格式化)。

页面属性操作(th:* 属性大全)

文本输出

  • th:text:转义输出(防 XSS,默认推荐)

  • th:utext:不转义输出,支持 HTML 标签

msg=这是<b>加粗</b>文字
<p th:text="#{msg}">原样输出&lt;b&gt;</p>
<p th:utext="#{msg}">显示加粗标签</p>

通用属性设置

th:attr:批量设置任意属性(少用,可读性差)

<img th:attr="src=@{img.jpg},title=#{img.title}">

专用属性(推荐):th:href/th:src/th:value/th:class/th:title

追加 / 前置属性:

  • th:classappend:追加 CSS 类(不覆盖原有 class)

  • th:styleappend:追加样式

布尔属性(checked/disabled/selected 等)

<input type="checkbox" th:checked="${user.vip}">

局部变量 th:with

在当前标签及子元素内定义临时变量:

<div th:with="format=#{date.format},now=${today}">
  <span th:text="${#calendars.format(now,format)}"></span>
</div>

循环迭代 th:each

基础用法

支持迭代:List/Set/ 数组 / Map/Iterator/Stream

<tr th:each="prod : ${productList}">
  <td th:text="${prod.name}"></td>
</tr>

迭代状态变量

自动携带状态,包含index(0开始)、count(1开始)、size、first、last、odd/even

<tr th:each="prod,stat : ${productList}" th:class="${stat.odd}?'odd-row'">
  <td>序号:${stat.count}</td>
</tr>

不手动命名时,迭代变量 +Stat自动生成:prodStat.odd

延迟加载变量

LazyContextVariable:仅当页面实际使用集合时才查询数据库,优化性能。

条件判断

th:if / th:unless

  • th:if:条件为 true 显示标签

  • th:unless:条件为 false 显示(等价th:if=!表达式

    判定 true 规则:非 null、非 0 数字、非 false/off/no 字符串

<a th:if="${#lists.size(prod.comments) > 0}" href="">查看评论</a>

th:switch / th:case

多分支匹配,th:case="*"为默认分支

<div th:switch="${user.role}">
  <p th:case="admin">管理员</p>
  <p th:case="user">普通用户</p>
  <p th:case="*">游客</p>
</div>

模板布局与片段复用(重点工程化)

片段定义 th:fragment

<!-- footer.html -->
<div th:fragment="copyright">©2026 小橙子</div>

三种引入指令区别

th:insert:将片段内容插入当前标签内部(保留父标签)

th:replace整体替换当前标签为片段(最常用)

th:include(3.1 已弱化,不推荐)

<div th:insert="~{footer::copyright}"></div>
<div th:replace="~{footer::copyright}"></div>

无 th:fragment 的片段引用

通过 CSS/id 选择器直接抓取页面元素:

<div th:replace="~{footer::#copy-id}"></div>

带参数片段(函数式布局)

<!-- 通用头部片段,接收title、links两个参数 -->
<head th:fragment="commonHeader(title, links)">
  <title th:replace="${title}"></title>
  <th:block th:replace="${links}"></th:block>
</head>
<!-- 页面调用,传入页面自定义标题、样式片段 -->
<head th:replace="~{layout::commonHeader(~{::title}, ~{::link})}">
  <title>商品列表</title>
  <link rel="stylesheet" th:href="@{/list.css}">
</head>

th:remove 移除元素(原型友好)

静态原型保留模拟数据,后端渲染自动删除:

  • all:删除标签 + 内部所有内容

  • tag:只删标签,保留子内容

  • all-but-first:保留第一行,删除其余模拟行(表格原型神器)

<!-- 仅原型展示,渲染自动删除 -->
<tr th:remove="all">
  <td>测试商品</td>
</tr>

布局继承

公共 layout 页面定义完整页面骨架,子页面替换标题、主体内容实现复用。

th:block 虚拟标签

唯一 Thymeleaf 专属合成标签,渲染后自动消失,仅承载逻辑,解决 table/tr、select/option 等不能嵌套 div 的场景:

<table>
  <th:block th:each="user : ${userList}">
    <tr><td th:text="${user.name}"></td></tr>
    <tr><td colspan="2">备注:${user.desc}</td></tr>
  </th:block>
</table>

注释与内联语法

三类特殊注释

标准 HTML 注释 <!-- 渲染后保留 -->

解析级注释 <!--/* 渲染直接删除 */-->:原型也不显示

原型专用注释 <!--/*/ 渲染取消注释 /*/-->:仅静态原型隐藏,后端正常渲染

文本内联(直接写在 HTML 文字中)

  • [[${xxx}]]:转义输出(等同 th:text)

  • [(${xxx})]:不转义输出(等同 th:utext)

<p>欢迎 [[${user.name}]]</p>
<!-- 关闭内联 -->
<p th:inline="none">文本[[变量]]原样展示</p>

JS / CSS 内联

<script th:inline="javascript"><style th:inline="css">

自动转义字符串、序列化 Java 对象为 JSON,支持注释式自然原型写法:

var name = /*[[${user.name}]]*/ "默认名称";

属性执行优先级(同标签多 th:* 执行顺序)

同标签多个 Thymeleaf 属性按优先级从上到下执行:

  1. th:insert /th:replace(引入片段)

  2. th:each(循环)

  3. th:if /th:unless /th:switch(判断)

  4. th:object /th:with(变量绑定)

  5. th:attr 系列(通用属性修改)

  6. th:href /th:src 专用属性

  7. th:text /th:utext(文本输出)

  8. th:fragment(片段定义)

  9. th:remove(删除元素)

高级特性

模板缓存

默认开启,缓存解析后的模板 AST 树,大幅减少 IO;开发环境建议关闭resolver.setCacheable(false),实时刷新页面。 可手动清空缓存:templateEngine.clearTemplateCache()

分离式模板逻辑(解耦 HTML 与动态逻辑)

  • 纯静态 HTML(设计师编写)+ 独立.th.xml逻辑文件

  • 通过 CSS 选择器注入 th:* 属性,完全分离页面结构与后端逻辑

  • 启用开关:resolver.setUseDecoupledLogic(true)

多解析器 / 多消息解析器

模板引擎可绑定多个解析器,按 order 顺序尝试读取模板;多消息解析器实现分层国际化。

文本模板模式(TEXT/JS/CSS 专用语法)

无 HTML 标签,使用[# th:each] [/]块语法处理纯文本、邮件模板。

开发常见避坑总结

  1. 静态原型正常,渲染丢失动态数据:检查缓存是否开启、前缀后缀路径是否正确;

  2. JS 内联变量报错:优先使用[[${}]]自动转义,不要直接拼接字符串;

  3. 循环序号错乱:使用状态变量stat.count而非下标;

  4. 国际化不生效:消息文件名、语言后缀匹配,检查文件编码 UTF-8;

  5. 页面无法访问:templates目录资源不能直接 URL 访问,必须走控制器跳转;

  6. HTML5 规范冲突:改用data-th-*替代th:*命名空间写法。

附录速查

常用工具对象高频方法

  • #temporals:JDK8 LocalDate/LocalDateTime 格式化

  • #dates:java.util.Date 工具

  • #numbers 金额、百分比、数字补零格式化

  • #strings 判断空、截取、大小写、转义

  • #lists/#aggregates 集合求和、平均值、判空

内置 Web 上下文对象

#ctx上下文、#locale语言、param请求参数、session会话、application全局域