1. 为什么SpringBoot项目里Thymeleaf依然值得你花时间如果你最近几年才开始接触SpringBoot可能会觉得Thymeleaf有点“老派”。毕竟现在前端工程化如火如荼Vue、React这些框架配合RESTful API才是主流JSP更是早就被扫进了历史的角落。那为什么我们还要花时间学一个“模板引擎”呢我刚开始也有这个疑问直到在几个真实的生产项目中被它“教做人”之后才彻底改变了看法。Thymeleaf的核心价值在于它完美地解决了**服务端渲染SSR**场景下的特定痛点。想象一下这些场景你需要快速开发一个内部管理系统功能不复杂但要求权限控制到按钮级别并且页面数据与后端模型强绑定或者你需要做一个邮件模板动态内容需要后端填充但又要保持HTML的可读性又或者你的项目就是传统的多页面应用MPA追求极致的首屏加载速度和SEO友好性。在这些场景下引入一套完整的前后端分离架构反而会带来额外的复杂度、部署成本和沟通开销。Thymeleaf就像一个瑞士军刀它不试图取代现代化的前端框架而是在SpringBoot生态中为服务端渲染提供了一套优雅、强大且与Spring深度集成的解决方案。它的“自然模板”特性是杀手锏。你写的Thymeleaf模板本身就是一段静态的、语法正确的HTML文件可以直接在浏览器里打开预览样式这极大地提升了开发和调试体验。当应用运行时Thymeleaf引擎再动态替换其中的属性生成最终的HTML。这种设计哲学让它与JSP那种“脚本片段”式的写法划清了界限更符合现代Web开发对清晰职责分离的追求。所以别把它看作一个过时的技术而应该视为你在SpringBoot全栈工具箱里的一件趁手兵器在合适的场景下它能让你事半功倍。2. 十分钟极速上手从零构建你的第一个Thymeleaf页面理论说再多不如动手跑一遍。我们从一个最干净的SpringBoot项目开始目标是展示一个带有动态时间的欢迎页面。这个过程会帮你理清Thymeleaf在SpringBoot中的基本工作流。2.1 项目初始化与依赖引入首先通过Spring Initializrstart.spring.io创建一个新项目选择Spring Web和Thymeleaf依赖。如果你用的是Mavenpom.xml里关键依赖是这样的dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- 开发工具支持模板热更新强烈建议加上 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-devtools/artifactId scoperuntime/scope optionaltrue/optional /dependency /dependencies注意spring-boot-starter-thymeleaf这个starter已经包含了Thymeleaf的核心库以及它与Spring的集成包无需再单独引入其他版本。2.2 控制器Controller的编写控制器是MVC中的“C”它负责处理请求准备数据并决定渲染哪个视图。在src/main/java下创建一个HelloControllerpackage com.example.demo.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; Controller // 注意是Controller不是RestController public class HelloController { GetMapping(/hello) public String sayHello(Model model) { // 1. 准备数据将服务器当前时间放入Model String currentTime LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)); model.addAttribute(serverTime, currentTime); // 2. 准备数据添加一个欢迎消息 model.addAttribute(welcomeMessage, 欢迎来到Thymeleaf世界); // 3. 返回逻辑视图名Thymeleaf会根据配置找到对应的HTML文件 return hello; } }这里有几个关键点ControllervsRestControllerController表明这个类是用来处理视图的方法返回的字符串对应模板名称。而RestController是Controller和ResponseBody的组合直接返回JSON/XML数据不经过视图解析器。用错了会导致Thymeleaf模板不渲染。Model对象这是Spring MVC提供的一个接口本质是一个Map。我们通过model.addAttribute(key, value)方法向其中添加数据。这些数据会被传递给模板引擎在模板中可以通过${key}来访问。返回值hello这个字符串叫“逻辑视图名”。Thymeleaf的视图解析器ThymeleafViewResolver会按照约定在src/main/resources/templates/目录下寻找名为hello.html的模板文件。2.3 模板Template的创建与数据绑定接下来在src/main/resources/templates/目录下创建hello.html文件。这是Thymeleaf模板的核心。!DOCTYPE html !-- 引入Thymeleaf命名空间这是必须的 -- html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title第一个Thymeleaf页面/title /head body h1 th:text${welcomeMessage}这里是静态的占位文本/h1 p服务器当前时间是span th:text${serverTime}2023-01-01 12:00:00/span/p hr h2Thymeleaf基础语法演示/h2 !-- 条件判断如果serverTime不为空则显示 -- div th:if${serverTime ! null} p时间数据已成功加载。/p /div !-- 遍历集合这里演示一个静态列表 -- ul li th:eachitem : ${#arrays.asList(Java, Spring, Thymeleaf)} span th:text${item}项目名/span /li /ul /body /html现在启动你的SpringBoot应用运行DemoApplication里的main方法然后在浏览器访问http://localhost:8080/hello。你应该能看到一个包含动态时间和欢迎信息的页面。核心语法解析xmlns:thhttp://www.thymeleaf.org在html标签中声明Thymeleaf的命名空间这样才能使用th:*系列的属性。th:text${welcomeMessage}这是最常用的属性。th:text会计算表达式${...}的值并用这个值替换掉标签的整个主体内容包括里面的静态文本“这里是静态的占位文本”。双引号内的${welcomeMessage}就是一个变量表达式它从Model中查找名为welcomeMessage的属性。“自然模板”体验如果你直接用浏览器打开这个hello.html文件不通过SpringBoot服务器你会看到静态的占位文本“这里是静态的占位文本”和“2023-01-01 12:00:00”。这就是“自然模板”对前端设计师非常友好。th:if条件判断属性。只有当表达式为真时所在的HTML标签才会被渲染到最终页面中。th:each循环迭代属性。这里我们使用了Thymeleaf的工具对象#arrays来创建一个临时的列表进行演示。item是每次迭代的循环变量。至此一个最简单的Thymeleaf应用就跑通了。你可能觉得这很简单但这就是所有复杂功能的基石。接下来我们要深入它的五脏六腑。3. 深入Thymeleaf表达式与基本语法不仅仅是${...}很多人用Thymeleaf只停留在${}变量表达式上这就像只用了Java的System.out.println。实际上Thymeleaf的表达式语言Thymeleaf Standard Expression Language非常丰富是高效开发的关键。3.1 四大核心表达式类型变量表达式Variable Expressions:${...}这是最常用的用于访问容器Model、Session等上下文中的变量。它支持OGNLObject-Graph Navigation Language和Spring ELSpring Expression Language语法因此功能强大。!-- 访问对象属性 -- p th:text${user.name}用户名/p !-- 访问Map -- p th:text${map[key]}Map值/p !-- 调用方法 -- p th:text${user.getDisplayName()}显示名/p !-- 使用Spring EL的三元运算符 -- p th:text${user.vip ? 尊贵的VIP会员 : 普通用户}会员状态/p选择变量表达式Selection Variable Expressions:*{...}这个表达式需要和th:object属性配合使用。它不是在全局上下文中查找而是在当前选定的对象上执行。这能极大地简化表单绑定等场景的代码。!-- 假设Model中有一个属性叫‘formUser’ -- form th:object${formUser} !-- 在th:object范围内*{name} 等价于 ${formUser.name} -- input typetext th:field*{name} / input typeemail th:field*{email} / /form使用*{...}可以让模板更简洁特别是在表单字段很多的时候避免了重复书写${formUser.xxx}。消息表达式Message Expressions:#{...}用于国际化i18n。它会从消息源如messages.properties文件中根据key获取对应的文本。这是实现多语言支持的标准方式。h1 th:text#{welcome.title}Welcome/h1 p th:text#{login.tips(${username})}Hello, user!/p在messages.properties文件中定义welcome.title欢迎页面 login.tips你好{0}链接表达式Link URL Expressions:{...}用于构建URL它会自动处理上下文路径context path是写链接和资源引用的最佳实践能避免硬编码。!-- 指向应用内的一个路径 -- a th:href{/user/list}用户列表/a !-- 带路径变量 -- a th:href{/user/detail/{id}(id${userId})}用户详情/a !-- 带查询参数 -- a th:href{/search(page1,size10)}搜索/a !-- 引用静态资源CSS, JS -- link th:href{/css/style.css} relstylesheet script th:src{/js/app.js}/script{...}表达式能确保你的应用即使部署在非根路径如http://domain.com/myapp下链接也能正确工作。3.2 常用属性处理器Attribute ProcessorsThymeleaf通过th:*属性来控制HTML标签的行为。除了上面用到的th:text还有一大批实用属性th:utext: 与th:text类似但不会对内容进行HTML转义。慎用除非你确定内容安全否则可能导致XSS攻击。!-- 假设content包含HTML标签 -- div th:utext${htmlContent}这里的内容不会被转义/divth:each: 循环前面已介绍。它有几个内置的状态变量非常有用tr th:eachuser, iterStat : ${userList} td th:text${iterStat.index}序号从0开始/td td th:text${iterStat.count}计数从1开始/td td th:text${iterStat.size}集合大小/td td th:text${iterStat.current}当前元素同user/td td th:text${iterStat.even}? ‘偶数行’ : ‘奇数行’奇偶行/td /trth:if/th:unless/th:switch/th:case: 条件判断。div th:if${user.active}用户已激活/div div th:unless${user.active}用户未激活/div div th:switch${user.role} p th:case‘admin‘管理员/p p th:case‘user‘普通用户/p p th:case*其他角色/p !-- 默认case -- /divth:attr: 动态设置一个或多个HTML原生属性。这是一个“万能”属性但通常有更具体的属性替代。img th:attrsrc{/images/logo.png}, title${logoTitle}, alt${logoTitle} / !-- 更优雅的写法是使用具体的属性处理器 -- img th:src{/images/logo.png} th:title${logoTitle} th:alt${logoTitle} /th:class/th:style: 动态控制CSS类和样式。!-- 根据条件添加/移除类 -- div th:class${isError ? ‘alert alert-danger‘ : ‘alert alert-info‘}消息/div !-- 追加类不覆盖原有class -- div classbase th:classappend${isActive ? ‘active‘ : ‘‘}项目/div !-- 动态设置样式 -- span th:style‘color:‘ ${isWarning ? ‘red‘ : ‘black‘}文本/span掌握这些表达式和属性你就能应对80%以上的日常模板开发需求。它们组合起来能让你以声明式的方式清晰地描述数据如何驱动视图。4. 实战进阶表单处理、片段复用与布局管理当页面变得复杂表单交互、头部尾部复用、统一布局这些需求就出现了。Thymeleaf通过与Spring MVC的深度集成提供了非常优雅的解决方案。4.1 表单绑定与数据回显这是Thymeleaf结合Spring MVC最强大的特性之一。假设我们有一个用户注册页面。1. 准备模型对象Model Object:public class UserForm { private String username; private String email; private Integer age; // 省略 getter/setter 和 toString }2. 编写控制器Controller:Controller RequestMapping(/user) public class UserController { // GET请求用于展示空表单 GetMapping(/register) public String showRegisterForm(Model model) { // 向Model中添加一个空的form对象th:object才能绑定 model.addAttribute(userForm, new UserForm()); return user/register; } // POST请求用于处理表单提交 PostMapping(/register) public String handleRegister(ModelAttribute(userForm) UserForm userForm, BindingResult bindingResult, // 用于接收校验结果 Model model) { // 1. 数据校验这里简单模拟实际应用JSR-303注解校验 if (userForm.getUsername() null || userForm.getUsername().trim().isEmpty()) { bindingResult.rejectValue(username, error.username, 用户名不能为空); } if (bindingResult.hasErrors()) { // 如果有错误返回表单页面Thymeleaf会自动回显已填数据和错误信息 return user/register; } // 2. 处理业务逻辑如保存用户... System.out.println(保存用户: userForm); // 3. 重定向到成功页面防止表单重复提交 return redirect:/user/success; } }3. 编写Thymeleaf表单模板 (register.html):!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title用户注册/title /head body h1用户注册/h1 !-- 表单绑定到 userForm 对象 -- form th:action{/user/register} th:object${userForm} methodpost !-- 用户名字段 -- div label forusername用户名/label !-- th:field 是关键它完成了三件事 1. 设置input的name属性为‘username‘ 2. 设置input的id属性为‘username‘ 3. 设置input的value属性为userForm.username的值数据回显 -- input typetext idusername th:field*{username} / !-- 显示该字段的错误信息 -- span th:if${#fields.hasErrors(‘username‘)} th:errors*{username} stylecolor:red;/span /div !-- 邮箱字段 -- div label foremail邮箱/label input typeemail idemail th:field*{email} / span th:if${#fields.hasErrors(‘email‘)} th:errors*{email} stylecolor:red;/span /div !-- 年龄字段 -- div label forage年龄/label input typenumber idage th:field*{age} / span th:if${#fields.hasErrors(‘age‘)} th:errors*{age} stylecolor:red;/span /div button typesubmit注册/button /form p当前表单对象的值span th:text${userForm}.../span/p /body /html核心机制解析th:object${userForm}将整个表单绑定到Model中的userForm对象。th:field*{fieldName}这是魔法发生的地方。它不仅仅是一个属性而是一个“复合属性处理器”。在渲染时它会根据字段类型自动生成正确的HTMLinput标签如type“text“,type“email“,type“number“并设置name、id和value。在表单提交后如果验证失败返回原页面它会自动将用户刚才提交的值即userForm对象中的值填充回输入框这就是数据回显。th:errors配合Spring MVC的BindingResult可以方便地显示每个字段的校验错误信息。#fields是Thymeleaf提供的工具对象用于访问绑定状态。这个流程极大地简化了表单开发你不再需要手动拼接name、id也不再需要写一堆JSTL标签来逐个回显字段值。一切都在th:field中自动完成。4.2 片段Fragments定义与引用当多个页面有相同的部分如页头、页脚、导航栏时代码复用就很重要。Thymeleaf提供了th:fragment和th:replace/th:insert来实现模板片段化。1. 定义公共片段创建一个/resources/templates/fragments/common.html文件。!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org body !-- 定义页头片段 -- header th:fragmentheader nav a th:href{/}首页/a | a th:href{/user/list}用户管理/a | a th:href{/about}关于我们/a /nav div th:if${session.user ! null} 欢迎span th:text${session.user.name}用户/span | a th:href{/logout}退出/a /div /header !-- 定义页脚片段 -- footer th:fragmentfooter p© 2023 我的公司. 版权所有./p p联系邮箱span th:text${contactEmail ?: ‘supportexample.com‘}默认邮箱/span/p /footer !-- 定义一个带参数的片段 -- div th:fragmentalert(type, message) div classalert th:classappend‘alert-‘ ${type} strong th:text${type}类型/strong: span th:text${message}消息内容/span /div /div /body /html2. 在页面中引用片段在具体的页面模板中比如index.html。!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title首页/title /head body !-- 插入页头th:replace 会用整个片段替换当前div标签 -- div th:replace~{fragments/common :: header} 这里的内容会被完全替换成header片段 /div main h1主页内容/h1 !-- 插入带参数的片段 -- div th:replace~{fragments/common :: alert(‘success‘, ‘操作成功‘)}/div div th:replace~{fragments/common :: alert(‘info‘, ${welcomeMsg})}/div /main !-- 插入页脚th:insert 会将片段插入到当前div标签内部 -- div th:insert~{fragments/common :: footer} !-- 页脚片段的内容会插入到这里面 -- /div /body /htmlth:replacevsth:insertvsth:include已废弃:th:replace用指定的片段替换掉宿主标签即外面的div。这是最常用的方式因为它能保持HTML结构干净。th:insert将指定的片段插入到宿主标签的内部。这意味着宿主标签本身会保留。th:include在Thymeleaf 3.0之前用于包含内容但语义模糊在3.0版本中已被th:replace和th:insert取代不建议使用。片段化让页面结构变得清晰维护公共部分就像维护一个独立的组件。4.3 布局管理Layout Dialect对于更复杂的页面结构比如每个页面都有相同的两栏布局侧边栏主内容区单纯使用片段引用会显得繁琐因为需要在每个页面重复编写html、head、body以及布局结构。这时Thymeleaf的布局方言Layout Dialect就派上用场了。它允许你定义一个“布局页”其他“内容页”只关注自己独有的部分。1. 添加布局方言依赖dependency groupIdnz.net.ultraq.thymeleaf/groupId artifactIdthymeleaf-layout-dialect/artifactId /dependencySpringBoot 2.1版本如果使用了spring-boot-starter-thymeleaf这个依赖通常已经自动管理了无需手动添加。2. 定义布局页Layout Page:创建/resources/templates/layout/base.html。!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org xmlns:layouthttp://www.ultraq.net.nz/thymeleaf/layout head meta charsetUTF-8 title layout:title-pattern$CONTENT_TITLE - $LAYOUT_TITLE默认标题/title link relstylesheet th:href{/css/main.css} !-- 内容页可以在这里添加额外的head内容 -- th:block layout:fragmenthead-scripts/th:block /head body header h1网站通用头部/h1 nav...导航菜单.../nav /header div classcontainer aside h3侧边栏/h3 !-- 定义一个可被替换的侧边栏区域 -- div layout:fragmentsidebar p这是默认的侧边栏内容。/p /div /aside main !-- 这是最重要的部分内容页的主体将插入到这里 -- section layout:fragmentcontent p默认内容。如果内容页没有定义‘content‘片段则显示这个。/p /section /main /div footer p通用页脚/p /footer script th:src{/js/common.js}/script !-- 内容页可以在这里添加额外的body脚本 -- th:block layout:fragmentbody-scripts/th:block /body /html3. 创建内容页Content Page:创建/resources/templates/user/list.html。!DOCTYPE html !-- 使用 layout:decorate 指定要使用的布局模板 -- html xmlns:thhttp://www.thymeleaf.org xmlns:layouthttp://www.ultraq.net.nz/thymeleaf/layout layout:decorate~{layout/base} !-- 指向布局文件 -- !-- 填充布局中的‘content‘片段 -- th:block layout:fragmentcontent h1用户列表/h1 table tr th:eachuser : ${users} td th:text${user.id}1/td td th:text${user.name}张三/td /tr /table /th:block !-- 填充布局中的‘sidebar‘片段替换掉默认内容 -- th:block layout:fragmentsidebar h3用户管理侧边栏/h3 ul lia th:href{/user/add}添加用户/a/li lia th:href{/user/search}搜索用户/a/li /ul /th:block !-- 向布局的‘head-scripts‘片段追加内容 -- th:block layout:fragmenthead-scripts link relstylesheet th:href{/css/user-list.css} /th:block !-- 向布局的‘body-scripts‘片段追加内容 -- th:block layout:fragmentbody-scripts script th:src{/js/user-list.js}/script /th:block /html工作原理当请求/user/list时Thymeleaf会先处理list.html。layout:decorate属性告诉引擎这个页面要用layout/base.html作为装饰布局。引擎会加载布局页然后用内容页中定义的各个layout:fragment块去替换布局页中对应的同名片段。最终生成的HTML是两者的合并。布局方言实现了真正的“模板继承”让页面结构管理变得井井有条特别适合中大型后台管理系统。内容页开发者只需要关心自己业务区域内的HTML和逻辑公共部分完全由布局页控制。5. 生产环境配置、性能优化与调试技巧当项目要上线时默认配置可能就不够用了。Thymeleaf提供了一系列配置项来优化性能和适应生产环境。5.1 关键配置项详解application.yml/properties在application.yml中配置Thymeleafspring: thymeleaf: # 模板模式默认是HTML一般不用改 mode: HTML # 模板编码默认UTF-8 encoding: UTF-8 # 前缀即模板文件存放目录默认classpath:/templates/ prefix: classpath:/templates/ # 后缀默认.html suffix: .html # 是否启用模板缓存。开发环境设为false修改模板后立即生效生产环境必须设为true以提升性能。 cache: false # 模板解析时是否检查模板位置是否存在开发时可设为true check-template-location: true # 是否启用Thymeleaf视图解析默认true enabled: true # 是否启用MVC Thymeleaf视图解析器默认true enabled-spring-mvc: true # 是否在渲染前排除空变量避免因变量为null导致渲染异常建议true exclude-empty-variables: true # 是否对非HTML模板也启用默认false enable-spring-el-compiler: false # 是否将SpringEL表达式先编译再求值可提升性能但可能增加启动时间生产环境可考虑true spring: el: compiler: mode: MIXED # 可选OFF, ON, MIXED # 模板解析器顺序数字越小优先级越高默认1 order: 1 # 渲染模板时的Content-Type默认text/html servlet: content-type: text/html生产环境必调项spring.thymeleaf.cachetrue这是最重要的性能优化。开启缓存后模板文件在应用启动时或第一次被访问时被解析并缓存到内存中后续请求直接使用缓存极大提升渲染速度。务必在生产环境开启。spring.thymeleaf.spring.el.compiler.modeMIXED启用Spring EL表达式编译。对于复杂的表达式编译后执行速度更快。MIXED模式是智能的对于简单表达式不编译复杂表达式才编译平衡了性能和内存。关闭开发工具DevTools确保生产环境打包时排除了spring-boot-devtools依赖或通过配置spring.devtools.restart.enabledfalse禁用它。5.2 模板解析与缓存机制深度剖析理解Thymeleaf的缓存机制能帮你更好地调试和优化。Thymeleaf的核心是TemplateEngine它内部有一个或多个TemplateResolver。解析流程当一个视图名如“hello“返回时TemplateEngine会通过TemplateResolver根据前缀(prefix)、视图名、后缀(suffix)拼接出模板文件的物理路径如classpath:/templates/hello.html然后读取、解析、渲染。缓存层级模板缓存缓存解析后的Template对象。由cache配置控制。开启后同一个模板文件在整个应用生命周期内只解析一次。片段缓存使用th:cacheable属性可以对模板片段进行缓存适用于那些数据不常变但渲染成本高的部分。不过SpringBoot默认集成的Thymeleaf没有开启片段缓存需要额外配置CacheManager。输出缓存Thymeleaf本身不提供完整的HTML输出缓存这通常需要借助Spring的缓存抽象如Cacheable注解在Controller层或专门的HTTP缓存头来实现。手动清理缓存主要用于开发/调试如果你在生产环境临时修改了某个模板文件不推荐可以通过注入TemplateEngine并调用其clearTemplateCache()方法来清理缓存或者重启对应的模板解析器。Autowired private TemplateEngine templateEngine; PostMapping(/admin/clearTemplateCache) public String clearCache() { // 注意这会影响性能仅用于管理后台的紧急操作 templateEngine.clearTemplateCache(); return 缓存已清理; }5.3 高效调试与问题排查实战开发过程中模板不生效、表达式报错是常事。掌握调试技巧能节省大量时间。1. 开启详细日志在application.yml中调整日志级别让Thymeleaf输出更多信息。logging: level: org.thymeleaf: DEBUG # 查看模板解析、缓存行为 org.thymeleaf.context: INFO # 如果表达式出错可以临时设为DEBUG查看更详细的栈信息2. 利用Thymeleaf的“模板原型”模式这是Thymeleaf最强大的调试功能。在开发环境即使模板有语法错误你也可以在浏览器中直接看到错误信息和高亮提示。 确保spring.thymeleaf.cachefalse并且应用以开发模式运行SpringBoot DevTools已启用。当模板渲染出错时Thymeleaf会生成一个“错误页面”其中包含出错的具体行号和位置。出错的表达式。完整的堆栈跟踪。出错处的模板源代码片段。3. 常见问题与解决方案问题页面显示“Property or field ‘xxx‘ cannot be found on null“原因表达式${obj.property}中的obj为null。解决使用安全导航运算符?.。例如将${user.name}改为${user?.name}这样如果user为null整个表达式会安静地返回null而不是抛出异常。或者在Controller中确保对象不为空。问题th:href或th:src生成的URL不对缺少上下文路径原因链接表达式{...}依赖于Servlet上下文路径。如果应用部署在非根路径如/myapp下需要确保服务器配置正确。解决在application.yml中配置server.servlet.context-path/myapp。Thymeleaf的{...}会自动处理。问题静态资源CSS, JS, 图片404原因SpringBoot默认将/static,/public,/resources,/META-INF/resources下的文件映射到根路径/。你的引用路径可能不对。解决使用{...}表达式引用资源如link th:href{/css/style.css} relstylesheet。确保CSS文件在src/main/resources/static/css/目录下。问题表单提交后th:field回显的数据不见了或者出现了奇怪的值原因这通常与Model中对象的生命周期和状态有关。提交表单后如果Controller中处理POST请求的方法没有重新将对象放入Model或者在重定向场景下模板就找不到这个对象。解决对于表单提交失败后返回原页面的场景有验证错误确保你在POST处理方法中无论成功与否都将表单对象或一个新的对象通过model.addAttribute(...)放回Model。Spring的ModelAttribute注解在方法参数上会自动完成这一步但如果你重定向了就需要使用RedirectAttributes的addFlashAttribute()来传递数据。4. 一个实用的调试技巧在模板中打印上下文所有变量有时你不知道Model里到底有什么可以在模板里临时加入div th:if${#ctx ! null} styledisplay:none; h3Debug Info:/h3 pModel Variables: span th:text${#ctx.variableNames}/span/p pRequest Parameters: span th:text${param}/span/p pSession Attributes: span th:text${session}/span/p /div#ctx是Thymeleaf的上下文工具对象。当然生产环境一定要移除或确保其不显示。Thymeleaf在SpringBoot生态中经过多年发展已经是一个非常成熟稳定的视图技术。它可能不是最“潮”的但在需要服务端渲染、快速开发、与Spring深度绑定的场景下其生产力和稳定性依然出类拔萃。理解其核心原理掌握配置优化和调试方法就能让它成为你项目中的可靠基石。