Thymeleaf核心原理与Spring Boot集成实战:从Web渲染到PDF生成
1. 项目概述为什么是Thymeleaf如果你是一个Java Web开发者尤其是Spring Boot的忠实用户那么“Thymeleaf”这个名字你一定不陌生。它几乎成了Spring官方推荐的视图模板引擎的代名词。但很多时候我们只是把它当作一个简单的HTML标签替换工具在pom.xml里加个依赖在HTML文件里写几个th:text就觉得万事大吉了。这其实大大低估了Thymeleaf的价值。我接触Thymeleaf快十年了从早期的Spring MVC到现在的Spring Boot看着它从一个“备选方案”成长为生态里的首选。它最大的魅力在于“自然模板”的理念你写的Thymeleaf模板文件本身就是一份完整、合法的HTML文件可以直接在浏览器里打开预览静态效果而无需启动后端服务。这对于前后端协作、原型设计和样式调试来说是巨大的便利。相比之下JSP必须经过容器编译Freemarker/Velocity的语法也破坏了HTML的完整性。最近的热搜词很有意思“springboot thymeleaf [pdfbox] flying-saucer-pdf”。这恰恰揭示了Thymeleaf的一个高阶应用场景动态PDF报告生成。开发者不再满足于生成网页而是希望利用成熟的模板技术来生成格式复杂的PDF文档比如合同、账单、报表等。Thymeleaf强大的模板处理能力结合PDF渲染库构成了一个非常优雅的解决方案。另一个热词“idea引入thymeleaf”则反映了新手在入门时遇到的第一道坎——环境配置和智能提示。所以这篇内容我想从一个老手的视角重新梳理Thymeleaf的“基础知识”。这些知识不仅仅是语法列表更是理解其设计哲学、避开常见陷阱、并能将其应用到像PDF生成这类复杂场景中的基石。无论你是刚入门的新手还是想深化理解的中级开发者相信都能从中找到你需要的东西。2. Thymeleaf核心设计哲学与工作流解析2.1 “自然模板”理念的深度解读“自然模板”是Thymeleaf的立身之本但很多人对其理解停留在“能直接用浏览器打开”的层面。这没错但更深层的价值在于开发流程的革新和职责的清晰分离。在传统JSP时代前端设计师或UI工程师和后端开发者的工作是强耦合的。设计师做好静态HTML后需要交给后端开发者“切”成JSP嵌入Java代码和标签库。这个过程容易出错且设计师无法独立验证最终效果。而Thymeleaf改变了这一点。设计师可以专注于生产标准的、带有样例数据的HTML文件。后端开发者拿到这个文件后无需改动其结构和样式只需在合适的标签上添加Thymeleaf属性如th:text、th:each即可。这些属性在静态浏览时会被浏览器忽略因为不是标准HTML属性显示的是HTML中硬编码的样例数据而在模板引擎渲染时这些属性会指导引擎用真实数据替换掉样例。举个例子一个商品列表项!-- 设计师给的静态原型包含样例数据 -- ul li商品名称样例手机 价格span2999/span元/li /ul !-- 后端开发者处理后变成Thymeleaf模板 -- ul th:eachproduct : ${products} li商品名称span th:text${product.name}样例手机/span 价格span th:text${product.price}2999/span元/li /ul后端开发者只添加了th:each和th:text属性没有破坏原有的HTML结构。设计师依然可以打开这个文件看到“样例手机价格2999元”的渲染效果从而验证样式。这种工作模式极大地提升了协作效率。注意为了获得最佳的静态预览效果务必使用完整的样例数据并确保Thymeleaf属性与样例数据在类型和格式上大致匹配。例如如果th:text最终会输出一个数字那么静态的样例最好也是一个数字这样可以避免预览时布局错乱。2.2 Thymeleaf模板处理引擎工作流理解Thymeleaf如何工作有助于你在遇到复杂问题时进行调试。它的处理流程可以简化为以下几个核心阶段模板解析Thymeleaf首先读取模板文件通常是.html并将其解析成一棵DOM树文档对象模型。这棵树精确反映了HTML的标签嵌套结构。Thymeleaf的解析器非常严格会强制将模板规范化为格式良好的XML/HTML这有助于提前发现标签未闭合等语法错误。方言处理这是Thymeleaf高度可扩展性的核心。方言Dialect定义了一组处理器Processor。每个处理器负责处理特定的Thymeleaf属性或元素。最核心的是标准方言Standard Dialect它提供了th:*系列的所有属性处理器。当引擎遍历DOM树时会为每个节点匹配所有已注册方言中的处理器。模板处理渲染这是最关键的阶段。引擎以“迭代”的方式处理DOM树。对于每个节点匹配到的处理器会按优先级执行。处理器能做很多事情替换内容如th:text处理器会计算其表达式如${product.name}将结果设置为该标签的文本内容并转义HTML特殊字符这是默认安全行为。th:utext则不转义。迭代th:each处理器会根据表达式如${products}遍历集合为集合中的每个元素复制当前标签及其子树的DOM结构并在复制的子树中将迭代变量如product放入上下文供子节点表达式使用。条件判断th:if/th:unless处理器根据表达式真假决定是否保留当前节点。属性修改th:attr可以动态设置任何属性更优雅的方式是使用特定属性处理器如th:href,th:src。片段引入th:insert/th:replace处理器用于模板布局和复用。输出处理完整个DOM树后引擎将最终的、包含真实数据的DOM树序列化写回为字符串输出给客户端浏览器或PDF渲染器。整个过程Thymeleaf上下文Context对象扮演了“数据搬运工”的角色。它内部维护了一个变量映射Map存放了你通过model.addAttribute放入的所有数据。表达式语言主要使用SpringEL则负责从上下文中按名称查找和计算这些值。2.3 标准方言与表达式语法SpringEL精要Thymeleaf的强大一半体现在其标准方言丰富的属性上另一半则体现在其支持的表达式语言上。在Spring Boot项目中默认集成的是Spring Expression Language (SpringEL)。核心属性速览与选择属性核心作用常用场景与注意事项th:text设置标签的文本内容并做HTML转义。显示动态文本。切记它会替换掉标签内所有原有内容包括子标签。th:utext设置文本内容不进行HTML转义。显示包含HTML标签的富文本内容。高危警告务必确保内容来源绝对可信否则会导致XSS攻击。th:value设置表单元素的value属性。input,textarea,select的数据绑定和回显。th:each循环迭代。渲染列表、表格行。关键循环状态变量iterStat可自定义名提供了index,count,size等有用信息。th:if/th:unless条件判断是否渲染此标签。根据数据状态控制UI元素的显示/隐藏。注意它是直接移除或保留DOM节点而非CSS的display:none。th:href动态生成链接地址。a标签的链接常结合{}链接表达式使用。th:src动态生成资源路径。img,script的src属性常结合{}使用。th:insert/th:replace模板片段复用。页面布局如头部、尾部、组件化。insert将片段插入当前标签内replace用片段替换当前标签。th:object声明表单绑定对象。与th:field配合简化表单字段绑定能自动生成name,id,value。SpringEL表达式实战要点在Thymeleaf中表达式被包裹在${...},*{...},{...},#{...},~{...}中。最常用的是变量表达式${}和链接表达式{}。变量表达式${}从上下文或各种作用域中获取变量。p th:text${user.name}用户名/p !-- 支持属性导航、方法调用、三元运算符等 -- p th:text${users[0]?.name ?: 默认用户}默认用户/p p th:text${#dates.format(article.publishTime, yyyy-MM-dd HH:mm)}时间/p?.是安全导航运算符避免users[0]为null时抛出异常。?:是Elvis运算符前面表达式为null时取后面的默认值。#dates是Thymeleaf工具对象#ctx, #locale, #strings, #numbers等提供了丰富的工具方法。选择表达式*{}通常用在th:object包围的范围内直接选择当前绑定对象的属性无需重复写对象名。div th:object${session.user} p姓名: span th:text*{firstName}张三/span./p p年龄: span th:text*{age}30/span./p /div链接表达式{}用于生成URL是Thymeleaf的一大亮点。它能自动处理上下文路径Context Path并且与Spring的RequestMapping完美配合。!-- 生成相对于上下文路径的URL -- a th:href{/products}产品列表/a !-- 生成带参数的URL -- a th:href{/product/detail(id${prod.id}, typenew)}查看详情/a !-- 生成静态资源URL会触发Spring资源处理链如版本号追加 -- script th:src{/static/js/app.js}/script实操心得在前后端分离架构中如果前端是独立部署上下文路径可能不同。此时可以在application.yml中配置spring.thymeleaf.prefix和静态资源映射或者使用绝对路径以/开头{}会忽略配置的prefix但更推荐通过反向代理统一路径。3. Spring Boot集成与高效开发环境搭建3.1 依赖引入与自动配置揭秘在Spring Boot中集成Thymeleaf简单到令人发指这得益于Spring Boot强大的自动配置Auto-Configuration。你只需要在pom.xml中添加一个starter依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency添加这个依赖后Spring Boot会自动为你配置好一个SpringTemplateEngine实例并设置好默认的视图解析器。默认的模板文件位置是classpath:/templates/默认后缀是.html。这些都可以通过application.properties或application.yml轻松修改。spring: thymeleaf: prefix: classpath:/templates/ # 模板文件查找前缀 suffix: .html # 模板文件后缀 mode: HTML # 模板模式推荐HTML非严格的HTML5还有LEGACYHTML5等 encoding: UTF-8 cache: false # 开发时务必设为false修改模板后立即生效。生产环境设为true提升性能。 servlet: content-type: text/html为什么推荐HTML模式而非LEGACYHTML5LEGACYHTML5模式使用一个更宽松的解析器NekoHTML能容忍一些不规范的HTML写法如未闭合的标签。而HTML模式使用更严格、更快的解析器。在现代前端开发中HTML规范已经很好建议直接使用HTML模式以获得更好的性能和严谨性。如果你的模板是从一些老旧的、格式不规范的页面迁移过来的可能会需要临时切换到LEGACYHTML5模式来通过解析但最终目标应该是清理模板使其符合规范。3.2 IDEA智能提示与Live Template配置“idea引入thymeleaf”这个热词反映了工具链支持的重要性。IntelliJ IDEA对Thymeleaf的支持非常出色但需要正确配置才能获得代码自动完成和语法高亮。确保Thymeleaf插件启用打开IDEA的Settings/Preferences-Plugins搜索“Thymeleaf”确保插件已安装并启用。关联Thymeleaf命名空间在HTML文件的html标签中添加Thymeleaf的命名空间声明。IDEA会自动识别并提供智能提示。html xmlns:thhttp://www.thymeleaf.org利用Live Template提升编码速度IDEA允许你自定义代码片段。你可以创建一些Thymeleaf的Live Template。打开Settings/Preferences-Editor-Live Templates。点击创建一个新的模板组比如叫Thymeleaf。在组内再点击创建Live Template。举例创建一个输出文本的模板Abbreviation缩写:thtDescription描述: Thymeleaf textTemplate text模板文本:th:text\${$EXPR$}$END$Applicable contexts适用上下文: 勾选HTML。这样在HTML文件中输入tht然后按Tab键就会自动生成th:text${}并将光标定位在表达式位置。配置模板根路径为了让IDEA的“跳转到声明”功能对~{...}片段表达式生效可以右键点击resources/templates目录选择Mark Directory as-Template Resources Root。3.3 开发阶段的热部署与调试技巧在开发过程中我们频繁修改模板和Java代码。如何避免反复重启应用Spring Boot DevTools这是首选方案。在pom.xml中添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-devtools/artifactId scoperuntime/scope optionaltrue/optional /dependencyDevTools会启用应用重启的触发器。默认情况下修改classpath下的文件包括templates和static目录会触发应用快速重启比冷启动快得多因为它使用了两个类加载器。更重要的是DevTools默认设置了spring.thymeleaf.cachefalse所以模板修改后刷新浏览器立即生效无需重启。禁用模板缓存如前所述在application.yml中设置spring.thymeleaf.cachefalse。这是开发时的必备选项。浏览器开发者工具Thymeleaf渲染的是服务器端HTML所以使用浏览器开发者工具的“元素”面板查看的是最终生成的DOM结构。如果你想看原始的Thymeleaf属性可以在“源代码”面板中查看服务器返回的HTML注意此时Thymeleaf属性可能已被处理掉。一个技巧是在出问题时可以在控制器中直接返回模板名称字符串然后查看浏览器接收到的完整HTML源码这有助于判断是数据问题还是模板语法问题。4. 超越Web页面Thymeleaf在PDF生成等场景的应用4.1 动态PDF生成的架构选型“springboot thymeleaf [pdfbox] flying-saucer-pdf”这个组合是社区实践出来的一个非常经典的PDF生成方案。我们来拆解一下每个组件的作用Thymeleaf负责将数据和模板结合生成格式良好的HTML字符串。这是它的本职工作做得非常好。Flying Saucer (openhtmltopdf)这是一个将HTML/CSS渲染为PDF的Java库。它支持相当完整的CSS 2.1规范以及部分CSS3能够将Thymeleaf生成的HTML完美地转换为PDF的页面描述。PDFBoxApache旗下的一个强大的PDF处理库。在这个组合中Flying Saucer底层通常使用PDFBox或iText来生成最终的PDF文件字节流。所以你可能会看到依赖中同时有flying-saucer-pdf和pdfbox。为什么不直接用PDFBox或iText写PDF因为用代码直接控制PDF的每一个元素文本位置、样式、分页极其繁琐尤其是对于复杂排版、带有表格、样式丰富的报告。而用HTMLCSS来描述样式和布局是前端开发者非常熟悉的方式效率高出几个数量级。Thymeleaf模板就是一份带有样式的HTML设计师可以参与制作后端只需关注数据填充。4.2 实现步骤详解与核心代码假设我们要生成一个用户订单的PDF发票。第一步准备Maven依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- 核心Thymeleaf PDF 渲染组合 -- dependency groupIdorg.xhtmlrenderer/groupId artifactIdflying-saucer-pdf/artifactId version9.1.22/version !-- 请使用最新稳定版 -- /dependency !-- Flying Saucer 会依赖 pdfbox 或 itext通常无需显式声明 --第二步创建PDF专用的Thymeleaf模板 (invoice-pdf.html)这个模板和普通Web模板几乎一样但需要特别注意打印样式。!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8/ title订单发票/title style typetext/css /* 打印样式是关键确保PDF布局正确 */ page { size: A4; margin: 2cm; bottom-center { content: 第 counter(page) 页 / 共 counter(pages) 页; font-size: 10pt; } } body { font-family: SimSun, Microsoft YaHei, sans-serif; /* 中文字体支持 */ font-size: 12pt; line-height: 1.5; } .invoice-header { text-align: center; margin-bottom: 2em; border-bottom: 2px solid #333; padding-bottom: 1em; } .invoice-table { width: 100%; border-collapse: collapse; margin-top: 1em; } .invoice-table th, .invoice-table td { border: 1px solid #ddd; padding: 8px; text-align: left; } .invoice-table th { background-color: #f2f2f2; } .total-row { font-weight: bold; font-size: 1.1em; } /* 避免在PDF中分页时切断行 */ tr { page-break-inside: avoid; } /style /head body div classinvoice-header h1销售发票/h1 p订单号: span th:text${invoice.orderNo}INV20230001/span/p p日期: span th:text${#dates.format(invoice.date, yyyy年MM月dd日)}2023-10-27/span/p /div div classcustomer-info h3客户信息/h3 p姓名: span th:text${invoice.customerName}张三/span/p p地址: span th:text${invoice.customerAddress}某市某区某街道/span/p /div h3商品明细/h3 table classinvoice-table thead tr th序号/th th商品名称/th th单价/th th数量/th th小计/th /tr /thead tbody tr th:eachitem, iterStat : ${invoice.items} td th:text${iterStat.count}1/td td th:text${item.productName}商品A/td td th:text${#numbers.formatDecimal(item.unitPrice, 1, 2)}100.00/td td th:text${item.quantity}2/td td th:text${#numbers.formatDecimal(item.subtotal, 1, 2)}200.00/td /tr /tbody tfoot tr classtotal-row td colspan4 styletext-align: right;总计/td td th:text${#numbers.formatDecimal(invoice.totalAmount, 1, 2)}0.00/td /tr /tfoot /table div classfooter stylemargin-top: 3em; p感谢您的惠顾/p p th:utext${invoice.companyFooter}公司盖章处br/***有限公司/p /div /body /html注意事项CSS打印支持Flying Saucer支持的是CSS 2.1的打印规范。page规则用于控制页面尺寸、边距和页眉页脚。page-break-before/after/inside属性控制分页。中文字体这是PDF生成中最常见的坑如果PDF中的中文显示为空白方块是因为系统没有找到CSS中指定的字体。解决方案是将中文字体文件如.ttf放入resources/fonts/目录并在CSS中使用font-face定义。font-face { font-family: MyFont; src: url(/fonts/simsun.ttf); /* 路径相对于classpath根Flying Saucer需要特殊处理 */ -fs-pdf-font-embed: embed; /* Flying Saucer专用属性嵌入字体 */ -fs-pdf-font-encoding: Identity-H; } body { font-family: MyFont; }更可靠的方式是在Java代码中通过ITextRenderer的FontResolver直接添加字体文件。第三步编写PDF生成服务类import org.springframework.stereotype.Service; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.ByteArrayOutputStream; import java.util.Locale; Service public class PdfGenerationService { private final TemplateEngine templateEngine; public PdfGenerationService(TemplateEngine templateEngine) { this.templateEngine templateEngine; } public byte[] generateInvoicePdf(InvoiceData invoiceData) throws Exception { // 1. 使用Thymeleaf渲染HTML字符串 Context context new Context(Locale.CHINA); context.setVariable(invoice, invoiceData); String htmlContent templateEngine.process(invoice-pdf, context); // 模板名为 invoice-pdf // 2. 使用Flying Saucer将HTML转换为PDF ByteArrayOutputStream outputStream new ByteArrayOutputStream(); ITextRenderer renderer new ITextRenderer(); // 3. 【关键】设置字体解决中文乱码问题 // 方式一通过FontResolver添加推荐 // ITextFontResolver fontResolver renderer.getFontResolver(); // fontResolver.addFont(classpath:/fonts/simsun.ttf, BaseFont.IDENTITY_H, BaseFont.EMBEDDED); // 方式二在CSS中使用font-face并确保字体文件可访问 // 需要配置一个资源解析器这里省略更推荐方式一。 renderer.setDocumentFromString(htmlContent); // 如果需要引用外部CSS文件或图片可以设置一个基础URL // renderer.getSharedContext().setBaseURL(file:/path/to/resources/); renderer.layout(); renderer.createPDF(outputStream); outputStream.close(); return outputStream.toByteArray(); } }核心要点templateEngine.process()方法的第一个参数是模板逻辑名称对应templates/目录下的invoice-pdf.html。ITextRenderer是Flying Saucer的核心类。setDocumentFromString接收渲染好的HTML字符串。中文乱码的终极解决方案必须在layout()之前通过ITextFontResolver将中文字体文件TTF添加到渲染器中并指定编码为BaseFont.IDENTITY_H支持Unicode。这是最稳定可靠的方法。第四步在控制器中调用并返回PDFRestController RequestMapping(/api/pdf) public class PdfController { private final PdfGenerationService pdfService; public PdfController(PdfGenerationService pdfService) { this.pdfService pdfService; } GetMapping(/invoice/{orderId}) public void downloadInvoice(PathVariable String orderId, HttpServletResponse response) throws Exception { // 1. 根据orderId查询业务数据构造InvoiceData对象 InvoiceData invoiceData getInvoiceDataById(orderId); // 2. 生成PDF字节数组 byte[] pdfBytes pdfService.generateInvoicePdf(invoiceData); // 3. 设置HTTP响应头告诉浏览器这是一个PDF附件 response.setContentType(application/pdf); response.setHeader(Content-Disposition, attachment; filename\invoice_ orderId .pdf\); response.setContentLength(pdfBytes.length); // 4. 将PDF字节流写入响应输出流 ServletOutputStream outputStream response.getOutputStream(); outputStream.write(pdfBytes); outputStream.flush(); } private InvoiceData getInvoiceDataById(String orderId) { // 模拟数据查询 InvoiceData data new InvoiceData(); data.setOrderNo(orderId); // ... 设置其他属性 return data; } }4.3 常见陷阱与性能优化字体问题如前所述中文乱码是首要问题。务必使用ITextFontResolver嵌入字体。对于商业字体请注意版权。CSS支持限制Flying Saucer不支持所有CSS3特性尤其是Flexbox、Grid等现代布局。设计PDF模板时应使用传统的表格、浮动和定位来实现布局。复杂的CSS效果可能需要测试。图片路径如果模板中有img th:src{/static/logo.png} /在PDF渲染时ITextRenderer需要能访问到这个图片资源。你需要通过renderer.getSharedContext().setBaseURL()设置一个基础URL或者将图片转换为Base64编码内嵌在HTML中data:image/png;base64,...。性能考量PDF渲染是CPU密集型操作尤其是处理复杂页面或高并发时。缓存模板确保生产环境spring.thymeleaf.cachetrue避免每次渲染都解析模板文件。缓存渲染结果如果同一份数据生成的PDF不常变化如日报可以考虑将生成的PDF字节数组缓存起来如使用Redis或Caffeine下次请求直接返回。异步生成对于耗时的PDF生成任务可以将其提交到线程池或消息队列中异步处理生成完成后通知用户下载或通过邮件发送。调整JVM参数PDF生成特别是使用iText时可能会消耗较多内存。确保JVM堆空间充足。5. 高级特性与最佳实践心得5.1 模板布局与片段复用对于Web应用保持一致的页头、页脚、导航栏是基本要求。Thymeleaf提供了强大的片段Fragment功能来实现布局。定义片段 (common.html):!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org body !-- 定义一个名为 header 的片段 -- div th:fragmentheader nav...导航栏HTML.../nav /div !-- 定义带参数的片段 -- div th:fragmentwelcome-message(username) p欢迎, span th:text${username}用户/span!/p /div div th:fragmentfooter footer...版权信息.../footer /div /body /html使用片段 (page.html):!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head.../head body !-- 插入 header 片段 -- div th:insert~{common :: header}/div !-- 等价于 th:replace -- !-- header th:replace~{common :: header}/header -- main h1主页内容/h1 !-- 插入带参数的片段并传递参数 -- div th:insert~{common :: welcome-message(${currentUser.name})}/div /main !-- 插入 footer 片段 -- div th:replace~{common :: footer}/div /body /htmlth:insert将片段插入当前标签内部。th:replace用片段替换当前标签。~{...}是片段表达式common是模板文件名不含后缀::后面是片段名。最佳实践将通用的布局组件如导航、页脚和UI组件如卡片、模态框拆分成片段极大提升代码复用性和可维护性。5.2 表单处理与数据绑定Thymeleaf与Spring MVC的表单绑定集成得天衣无缝。form th:action{/user/save} th:object${user} methodpost !-- th:field 会自动生成 id, name, value并与 th:object 绑定 -- input typetext th:field*{username} classform-control/ div th:if${#fields.hasErrors(username)} th:errors*{username} classalert alert-danger/div input typeemail th:field*{email}/ div th:if${#fields.hasErrors(email)} th:errors*{email}/div select th:field*{country} option value-- 请选择 --/option option th:eachc : ${countries} th:value${c.code} th:text${c.name} 中国 /option /select button typesubmit保存/button /formth:object${user}声明了表单绑定的模型属性。th:field*{username}是核心。它会生成nameusername、idusername并且value值会自动从绑定的user对象的username属性获取。在表单提交后回显时这个值也会自动填充。th:errors*{username}会显示该字段的验证错误信息需要配合Spring的Valid注解和BindingResult使用。#fields是Thymeleaf提供的与Spring集成的工具对象用于访问错误状态。5.3 国际化与工具对象Thymeleaf对国际化的支持非常直接。消息表达式#{}从国际化资源文件中读取文本。h1 th:text#{page.home.title}首页/h1 p th:text#{page.welcome(${user.name})}欢迎张三/p在resources/下创建messages.properties默认、messages_zh_CN.properties等文件。Spring Boot会自动管理MessageSource。工具对象Thymeleaf内置了大量工具对象在表达式里以#开头。#strings: 字符串工具isEmpty,substring,toUpperCase等。#lists,#sets,#maps: 集合工具。#numbers: 数字格式化工具formatDecimal非常常用。#dates: 日期格式化工具format,createNow等。注意在Java 8项目中更推荐在后台将LocalDateTime等类型格式化为字符串再传给模板或者自定义工具类。#calendars: 日历工具。#aggregates: 聚合函数sum,avg等可以在th:each循环中直接使用。p th:text${#strings.toUpperCase(product.name)}/p p th:text${#numbers.formatDecimal(total, 0, 2)}/p p th:text${#dates.format(now, yyyy-MM-dd HH:mm)}/p !-- 计算列表总和 -- p th:text${#aggregates.sum(products.![price])}/p5.4 安全与XSS防护Thymeleaf在安全方面做了很好的默认处理。文本转义th:text默认会对表达式结果进行HTML转义。这是防止XSS攻击的第一道防线。除非你完全信任内容来源否则永远不要使用th:utext。URL转义{}链接表达式在生成URL时会对参数进行URL编码。JavaScript内联Thymeleaf支持在JavaScript脚本块中内联数据并且会自动进行JavaScript转义。script th:inlinejavascript var userId [[${user.id}]]; // 会被正确转义为JS字符串或数字 var userName /*[[${user.name}]]*/ 默认名; // 注释中的是静态回退值 /script使用th:inlinejavascript和[[...]]语法Thymeleaf能智能地处理类型将字符串用引号包裹数字则直接输出并转义特殊字符。我个人在实际项目中的体会是Thymeleaf的“自然模板”特性在团队协作中带来的收益远超预期。它让前端和后端的界限在视图层变得清晰而友好。而对于PDF生成这类需求它更是提供了一个比直接操作PDF库高效得多的解决方案。关键在于你要真正理解它的工作流程处理好字体、资源路径这些细节并善用片段化和工具对象来构建可维护的模板。当你能熟练地将Thymeleaf从简单的页面渲染扩展到邮件模板、报表导出等场景时你会发现这个“基础知识”扎实的模板引擎实际上蕴藏着巨大的生产力。