尧图精选

SpringBoot整合Thymeleaf实战:从选型到模板语法全解析

🕒 发布时间:2026/10/1 18:15:56 📁 来源:尧图网络
先把一个最关键的事实说清楚SpringBoot整合Thymeleaf属于服务端渲染SSR的经典组合它解决的是“后端有数据、要快速出页面”的问题。如果你在做后台管理系统、内容展示型门户、带SEO需求的官网或者不想把前端拆成Vue/React工程SpringBoot Thymeleaf几乎是最省事的选择。Thymeleaf在SpringBoot官方推荐里地位很稳它有starter配置少模板还能直接用浏览器打开看效果不需要额外启动容器。这篇文章我会从选型逻辑讲起带你把依赖引入、配置项、Controller对接、模板语法、热更新、常见报错全部过一遍整个过程结合我实际踩过的坑尽量让你看完能直接照着做一个能跑的页面出来。1. 为什么选Thymeleaf而不选JSP或FreeMarker1.1 JSP的衰落和Thymeleaf的崛起SpringBoot官方文档里明确建议不要再优先考虑JSP。原因是JSP最终会被编译成Servlet类依赖Servlet容器打包成可执行Jar后JSP文件放在src/main/webapp里用SpringBoot内嵌Tomcat跑起来经常遇到模板找不到、类加载器错乱这类问题。而且JSP在前后端分离的大趋势下生态已经明显退化写起来也没有任何模板引擎层面的“现代感”。Thymeleaf能上位核心是它和HTML标准兼容。模板文件本身就是合法的HTML页面你用浏览器直接打开.html看到的是静态效果后端数据一旦渲染进来页面自动替换成动态内容。这种特性对前端切页面的配合、对后端的自测、对调试来说都省了一大截事。FreeMarker的语法符号和真实HTML混在一起不启动应用根本看不出渲染结果这一点没法跟Thymeleaf比。这里有很实际的价值前端给你一套静态页面你直接把html文件丢进模板目录把CSS、JS路径改对把数据标签替换进去页面样式基本不会乱。1.2 服务端渲染在什么场景下依然不可替代现在很多人一听到模板引擎就问“你怎么不用Vue”实际上要看场景。内部管理系统、运营后台、报表系统这类项目用户量不大页面交互简单用SPA反而要处理跨域、Token刷新、路由守卫、构建部署一整套工程化问题。SpringBoot Thymeleaf天然就是同源部署Controller返回一个视图名浏览器直接拿HTML任何接口权限都能通过原生Session、拦截器或Spring Security控制几乎没有额外学习成本。另外一个不可替代的场景是SEO。爬虫对单页应用的渲染支持虽然一直在进步但服务端直接输出完整HTML始终是搜索引擎最愿意看到的形态。Thymeleaf能把首屏内容和整页数据一次性输出到HTML里方便搜索引擎抓取这是前后端分离方案天然吃亏的地方。所以你评估技术选型时先想清楚你的项目是给谁用的给运营和内部人员用的后台SSR的开发和维护成本远低于一套独立前端工程给公网用户且看重搜索引擎来源的Thymeleaf也比SPA友好得多。2. 环境准备与依赖引入2.1 版本选型和“版本太高”问题先做环境规划。JDK建议8起步当前大多数企业的生产环境还在JDK8和JDK11之间SpringBoot2.7.x是兼容性最舒服的版本序列。如果你的公司强制要求SpringBoot3.x那JDK至少要到17且Thymeleaf的spring-boot-starter-thymeleaf会自动适配对应版本不需要手动指定Thymeleaf版本号这点放心交给SpringBoot的依赖管理机制处理。我要专门说一下热词里出现的“SpringBoot版本太高”这类问题。很多人从SpringBoot2.x升到3.x之后发现页面渲染500多半不是Thymeleaf本身的问题而是Javax到Jakarta命名空间迁移带来的连锁反应。SpringBoot3.x里javax.servlet全部变成了jakarta.servlet如果你项目里还手动引了旧的javax.servlet-api依赖或者有一些老的自定义拦截器、过滤器用了旧的Servlet API直接报Bean创建异常或者类型不匹配。我的建议是新项目用SpringBoot3.x JDK17老项目升版本的时候先检查一遍项目里有没有直接引用javax.*有就先清理干净再去处理代码兼容。2.2 Maven和Gradle依赖坐标Maven项目里只需要一个starter即可搞定坐标如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependencyGradle版本implementation org.springframework.boot:spring-boot-starter-thymeleaf除了这个starter之外正式写页面通常还需要引入spring-boot-starter-web这个不用多解释Controller依赖它。如果你想做表单校验再补一个spring-boot-starter-validationThymeleaf的th:errors可以很优雅地展示校验信息这个后文会提。还有一个小细节如果你的项目里同时有spring-boot-starter-web和spring-boot-starter-thymeleaf并且用了HTML5模式不需要手动引入thymeleaf-layout-dialect之外的布局依赖。SpringBoot已经把默认模板位置classpath:/templates/、默认后缀.html、默认编码UTF-8这些配置项全部预置好了零配置就能跑起来第一个页面。3. 配置文件详解路径、缓存和视图解析3.1 application.yml里的核心配置SpringBoot对Thymeleaf的自动化配置足够多但有几个配置项我建议每次新建项目都随手写上避免后面调试时被缓存和编码折磨。spring: thymeleaf: prefix: classpath:/templates/ suffix: .html cache: false encoding: UTF-8 mode: HTMLprefix指定模板所在目录SpringBoot默认就是classpath:/templates/可以省略不写。suffix默认.html也可以省略。真正关键的是cache: false和mode: HTML。cache: false告诉模板引擎不要缓存渲染结果这样开发阶段改完HTML刷新页面就能看到效果不需要重启应用。生产环境记得改回true否则每次请求都重新解析模板性能和磁盘IO都会受影响。mode: HTML是告诉解析器按HTML5解析模板文件默认值就是HTML但在某些旧版本里默认值是HTML5写法已经废弃你手动配成HTML最保险。3.2 模板目录和静态资源目录的不同职责很多人最容易困惑的一件事是CSS、JS、图片到底放哪里HTML模板又放哪里。SpringBoot的约定是src/main/resources/templates/存放Thymeleaf模板文件即动态页面主体。src/main/resources/static/存放CSS、JS、图片、字体等静态资源。模板文件放在templates下Controller返回视图名时会自动拼上前缀和后缀比如返回index就去找templates/index.html。静态资源放在static下浏览器直接通过根路径访问比如static/css/style.css页面上写成/css/style.css就能引到。Thymeleaf模板在引用静态资源时建议用th:href{/css/style.css}而不是href/css/style.css。{...}这个语法是Thymeleaf上下文路径相关的URL表达式。如果你的应用后来部署在某个二级路径下比如http://ip:8080/myapp/用原生/css/style.css会直接404但用{/css/style.css}Thymeleaf会自动把项目根路径拼上去。很多人测试环境一切正常部署到Nginx二级目录下面全部样式丢失十有八九就是这里写死了绝对路径。3.3 多个视图解析器时的行为差异实际项目里偶尔会遇到SpringBoot同时接Thymeleaf和FreeMarker或者同时有JSP和Thymeleaf的场景。SpringBoot默认按依赖顺序和Order属性自动装配ViewResolver。Thymeleaf的ThymeleafViewResolver默认order是Integer.MAX_VALUE中的减一优先级比较低。如果你项目里同时存在JSP和Thymeleaf两个视图解析器返回的视图名会先被InternalResourceViewResolver拦截导致Thymeleaf模板永远不被命中。我遇到过最典型的情况是公司老项目从JSP向Thymeleaf迁移两边依赖都留着Controller返回index时直接去解析成了/WEB-INF/jsp/index.jsp然后报404。排查了半天才发现是视图解析器顺序的问题。解决办法是手动设置ThymeleafViewResolver.setOrder(1)把优先级提到最前面。你要是准备从JSP平滑迁移到Thymeleaf这一条务必记住。4. 第一个能跑的页面Controller与模板对接4.1 写一个最简单的Controller我先从最基础的案例讲起这个过程我建议你亲手敲一遍感受Thymeleaf的数据传递模式。Controller public class IndexController { GetMapping(/index) public String index(Model model) { model.addAttribute(title, SpringBoot整合Thymeleaf第一课); model.addAttribute(tips, 把数据放进Model模板里用th:text输出); return index; } }对应页面src/main/resources/templates/index.html这样写!DOCTYPE html html langzh-CN xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title th:text${title}默认标题/title /head body h1 th:text${title}这里是静态兜底内容/h1 p th:text${tips}没有数据时会展示这段静态内容/p /body /html这里有一个精髓xmlns:thhttp://www.thymeleaf.org这行命名空间声明作用不是帮后端渲染而是让IDEA编辑器对th:开头的标签不做红色报错同时让静态页面在浏览器里也能正常显示。标签里的纯文本部分是“静态兜底内容”后端数据渲染时会被替换掉。你前端切页面的时候可以把这部分当成占位后端接数据时再把th:属性补上配合效率很高。Controller里的Model是SpringMVC传给视图的数据容器一个addAttribute就是一个键值对模板里${title}就直接取出这个值。这一步是整个整合的核心链路Controller放值模板取值。4.2 Request域和ModelAndView的另一种写法除了Model还可以直接通过ModelAndView完成数据和视图的封装这一个在旧代码里非常常见。GetMapping(/modelAndView) public ModelAndView modelAndView() { ModelAndView mv new ModelAndView(detail); mv.addObject(user, new User(张三, 20)); return mv; }ModelAndView的构造函数第一个参数就是视图名addObject等价于model.addAttribute。这种写法好处在方法内部自包含数据和视图打包在一起函数式代码里看起来更直观。不过它对返回值类型有约束后端接口想返回JSON时就得另写方法所以现在的项目更倾向于直接返回StringModel的组合。两种方式都理解看到老代码不慌就够了。4.3 模板里遍历集合和分支判断实际开发里大部分页面都要循环输出列表。假设你从数据库查出用户列表放进Model模板里的写法如下table thead tr th姓名/th th年龄/th th状态/th /tr /thead tbody tr th:eachuser : ${userList} td th:text${user.name}/td td th:text${user.age}/td td span th:if${user.status 1} th:text启用/span span th:unless${user.status 1} th:text禁用/span /td /tr /tbody /tableth:each是循环指令语法含义是遍历userList集合每次迭代的项名称为user。th:if和th:unless是条件判断的一对组合th:if条件成立就输出该标签th:unless相反。这里值得说的是判断条件里推荐用比较数字用equals比较字符串。比如${user.name admin}Thymeleaf表达式引擎底层会调用Object.equals做字符串比较所以直接写是安全的不会像Java一样比较引用地址这一点比JSP EL表达式要友好。5. 核心语法盘点与应用场景5.1 表达式类型变量表达式、选择表达式和URL表达式Thymeleaf表达式主要分成几类初学时可以把它们记成三种符号${...}、*{...}、{...}。${...}叫变量表达式从Model或请求域中读取数据这是用得最多的一个。*{...}叫选择表达式必须配合th:object使用。比如div th:object${user} p th:text*{name}/p p th:text*{age}/p /divth:object把user对象选出来内部子标签就能用*{name}替代${user.name}写法更简洁。当你一个页面要展示某个对象的多个字段时选择表达式能省下重复写对象名的功夫。{...}就是前文说的URL表达式专门用来生成链接、拼接上下文路径用在th:href、th:src等属性上。大家把这三类记忆清楚之后看模板里的标签就不会懵了凡是属性名带th:开头右边写的是表达式语法不再是我们Java代码里的字符串拼接。5.2 文本输出、属性替换和HTML片段引入文本输出最基础的方式是th:text它会HTML转义把script之类的字符转成安全字符串防止XSS注入。如果你的后台数据是富文本编辑器生成的要输出带HTML标签的内容就得用th:utext它不转义直接输出原始HTML。这里是一个安全分水岭用户输入内容永远优先用th:text只有你确信内容是可信来源时再用th:utext否则等于给攻击者开了一扇门。属性替换同样频繁。最常见的按钮禁用、表单回显、图片地址替换input typetext th:value${user.name} / button th:disabled${user.status 0}提交/button img th:src{/images/avatar.png} alt头像 /th:value用于给表单控件的value属性赋值th:disabled会根据表达式结果动态决定是否给按钮添加disabled属性th:src则常配合URL表达式使用。页面复用方面Thymeleaf原生的方式有th:insert和th:replace。两者区别是th:insert是在标签内部插入引入的片段th:replace是直接用引入的片段替换整个标签。实际项目里通常配th:fragment定义一个公共片段!-- commons.html -- div th:fragmentheader h2公共页头/h2 /div !-- 使用页面 -- div th:replacecommons :: header/div这里的commons :: header意思是从commons.html模板里找名字为header的片段然后(th:replace)替换掉当前div。公共导航栏、页脚、版权信息都可以抽到这种公共模板里后续维护只需要改一个文件能少走很多弯路。6. 热更新配置改完页面立刻生效6.1 纯Thymeleaf层面怎么开启热更新先讲最核心的配置前文已经提过spring.thymeleaf.cache: false。只要有这一条模板文件每次请求都会被重新解析改HTML的内容保存后直接刷新浏览器就能看到新效果完全不需要重启。但要注意这个只对模板文件生效Controller代码、Java类的修改还是需要重新编译并重启应用。还有一个容易被忽略的点IDEA里写templates目录下的HTML修改后如果不触发编译保存文件并不能让SpringBoot感知文件变更。有些场景下你需要手动Build - Build ProjectCtrlF9把文件重新复制到target/classes/templates下刷新页面才看到变化。如果你嫌手动麻烦后面讲DevTools可以彻底解决。6.2 SpringBoot DevTools的完整配置方案SpringBoot提供的开发者工具DevTools可以在代码文件变更后自动重启应用模板文件变更后还能配合LiveReload让浏览器自动刷新页面。依赖坐标如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-devtools/artifactId scoperuntime/scope optionaltrue/optional /dependency引入DevTools后Java代码有改动时SpringBoot会自动重启不是重新编译它只是用两个类加载器快速重启模板文件改动时因为cache: false直接生效。这里要特别说明DevTools的自动重启不等于我们手动重启启动速度通常非常快因为大部分类都被缓存了。生产环境不要带DevTools它的optional标签和runtime范围就是确保常规打包时不会进入生产Jar包。如果你用IDEA还需要开启自动编译。设置路径是Settings - Build, Execution, Deployment - Compiler勾选Build project automatically。老版本还要在Settings - Advanced Settings里勾选Allow auto-make to start even if developed application is currently running。不勾这个DevTools感知不到文件变化。6.3 浏览器自动刷新插件和实战体验DevTools会启动一个LiveReload服务器浏览器安装LiveReload扩展后页面文件变化就能自动刷新。Chrome商店直接搜LiveReload即可。实际体验是改了CSS、HTML保存后1秒左右页面自动刷新这个效率提升对调整页面样式来说非常明显。但我必须说一个我自己的习惯开发阶段还是倾向于手动刷新浏览器。原因很现实——LiveReload在同时开了多个页面的时候全部页面一起刷新有时候后端返回一个异常页自动刷新反而干扰定位问题。你可以先用cache: false DevTools的组合确定模板和静态资源改动都生效了再到需要频繁改样式的阶段把LiveReload打开。7. 常见问题排查与避坑指南7.1 页面404和500的定位方法碰到页面404先看Controller是否标注了Controller而不是RestController。RestController是Controller加ResponseBody的组合方法返回值会被当JSON写进响应体而不会去找视图渲染。Thymeleaf开发里最常见的第一坑就是习惯性把Controller写成RestController浏览器里显示SpringBoot默认JSON结构页面路径却始终404。排除这个因素后再检查返回的视图名和模板文件路径是否一致。比如Controller返回index模板文件必须是templates/index.html少一个层级都不行。还有一种情况是模板文件放到了static目录下这种直接404因为Thymeleaf只认templates目录。页面500则通常分两类一类是模板语法错误比如th:each写成了each或者表达式里的变量名和Model里的key不一致页面报SpelEvaluationException之类的错误这类能看到异常堆栈定位相对容易另一类是模板内部数据对象为null比如${user.name}但user是null直接NPE。后者建议在Controller里提前判空页面里也可以用${user ! null ? user.name : 未知}这种三元表达式兜底。7.2 静态资源404和CSS样式丢失页面能打开但样式全丢了优先检查HTML里引用CSS的方式。实际审查时直接在浏览器开发者工具里看CSS请求的URL如果显示带项目context-path的路径而你的CSS引用还是绝对路径那基本可以断定是路径问题。把href/css/style.css改成th:href{/css/style.css}即可。另一个常见情况是静态资源文件确实存在但Maven打包后没被复制到target/classes/static目录。检查pom.xml里是否正确配置了资源目录。默认SpringBoot应该会自动处理但某些公司内网私服或者自定义parent pom会覆盖资源定义。我建议你在项目根目录执行一次mvn clean package然后查看target/classes/static目录下文件是否存在这一步能快速排除打包问题。7.3 中文乱码的前后端正解页面和接口都出现中文乱码时大概率是编码不一致。前端的HTML里要有meta charsetUTF-8后端配置文件里spring.thymeleaf.encoding: UTF-8同时确保Java源文件本身是UTF-8编码。如果你用IDEA右下角查看文件编码格式是否为UTF-8。三个位置的编码保持一致之后乱码基本消失。如果你服务器是Linux且部署后乱码还要检查启动参数里有没有-Dfile.encodingUTF-8某些发行版默认是POSIX或者ANSI_X3.4-1968需要在启动脚本里强制执行UTF-8。7.4 Thymeleaf与SpringBoot3.x的兼容问题SpringBoot3.x中使用Thymeleaf需要注意Spring Security相关的表达式还是写sec:authorize但依赖不再是thymeleaf-spring5而是thymeleaf-spring6包名。如果你从网上复制的老配置引了thymeleaf-extras-springsecurity5SpringBoot3.x下直接启动失败。正确做法是引入dependency groupIdorg.thymeleaf.extras/groupId artifactIdthymeleaf-extras-springsecurity6/artifactId /dependency如果你的项目没有用Spring Security做权限控制那连这个依赖都不用引sec:相关语法也用不上。7.5 视图解析器顺序和多余依赖前文提到新旧项目迁混合场景下视图解析器优先级问题这里给出一个快速检查思路如果项目同时引入了spring-boot-starter-thymeleaf和spring-boot-starter-web而templates目录下模板就是找不到试着在启动日志里搜索ViewResolver相关的输出看看注册了哪些视图解析器。通常Thymeleaf会注册一个名字为thymeleafViewResolver的Bean如果日志里没有说明starter未被加载。再检查pom.xml里是否存在spring-boot-starter-thymeleaf被exclusion排除的情况。只要确认依赖存在、目录正确、Controller为Controller90%的模板404问题都能被这几个步骤命中。最后再分享一个我自己折腾出来的经验。Thymeleaf的模板代码要养成“静态兜底”习惯——th:text标签之间一定要写默认文案不要留空。A标签的th:href配上默认href#图片的th:src配一个默认占位图。这样做最直接的好处是前端拿到HTML就能预览整体效果不用起后端服务后端调试时如果数据缺失页面也不会丑陋到没法看。这样一个组合在SpringBoot整合Thymeleaf的开发流程中反而不是可有可无的点它直接决定了页面开发阶段前后端协作是否顺畅。你在自己项目里按这个标准去写模板坚持一个月会发现页面和数据的调试效率比大多数人要高出一截。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →