Spring Boot整合FreeMarker 404排查:从依赖到视图解析 在Spring Boot项目中整合FreeMarker模板引擎最让人头疼的问题就是页面跳转时直接甩你一个404。这个问题我在接手的项目里碰见过不止一次也帮一位开发者排查过整整一下午最后发现只是依赖没引全。这篇就把我从定位到解决这一整套思路整理出来从配置到排查一条龙讲透适合正在踩坑的你也适合想弄清Spring Boot视图解析原理的入门者。1. 项目基础与404现象全貌1.1 两类404的含义排查前必须先分清楚这里说的404到底是哪一种。第一类是最常见的HTTP 404浏览器地址栏敲下去返回给你的是Tomcat或Spring Boot默认的错误页面上面写着“Whitelabel Error Page”意思是服务端压根没找到对应的URL路由。第二类是模板引擎的404请求地址本身能命中Controller但Controller返回的视图名对应不到任何.ftl文件这时候FreeMarker会抛出“Template not found”之类的错误浏览器端表现出来常常也像404。这两类问题混在一起特别容易干扰排查因为现象一样解决思路完全相反。前者要去查路由映射后者要去查模板位置和后缀。我习惯先问自己一个问题这个地址是第一次访问就404还是页面里点击跳转之后才404前者多半是路由或依赖问题后者多半是模板路径或视图解析配置问题。先分清方向再动手能省一半时间。1.2 我遇到的高频起因根据我接触过的案例分析Spring Boot整合FreeMarker出现404大多逃不出这么几个坑一是依赖没引全或者引错了。很多人只加了spring-boot-starter-freemarker但项目本身连spring-boot-starter-web都没有Spring MVC都没起来谈何视图渲染。二是模板文件放错位置。FreeMarker默认约定加载classpath:/templates/下的模板如果你把.ftl文件放到static、resources根目录甚至自定义文件夹里视图解析器找不到文件404就来了。三是Controller写错注解。这是最隐蔽的坑把Controller写成了RestController导致返回的字符串被当成HTTP响应体直接输出页面不跳转反而在浏览器里看到一堆纯文本或者空白页。四是配置文件里的前缀后缀设置互相矛盾。有人同时设置了spring.mvc.view.prefix和spring.freemarker.suffix两个体系混用视图解析器最后拼接出来的路径根本不是你的真实文件路径。五是返回的视图名和文件名不一致。Controller里return index但磁盘上的文件叫index.html或者index.ftlh自然找不到。先把这个全景图放在脑子里下面我们再一层层拆开看每个坑背后到底发生了什么。2. 核心原因拆解从依赖到视图解析机制2.1 FreeMarker在Spring Boot里的职责FreeMarker在Spring Boot里扮演的是“视图渲染层”的角色。你可以把整个流程理解成浏览器请求某个URLSpring MVC的DispatcherServlet拿到请求后交给对应的Controller方法处理Controller返回一个逻辑视图名比如list然后视图解析器接手在项目中定位list对应的模板文件把模板渲染成HTML再返回给浏览器。FreeMarker只负责“渲染”这一步。它不关心URL路由也不关心数据从哪里来它只按照模板文件的内容把Controller传过来的模型数据填充进去生成最终的HTML。所以当页面跳转404时问题不一定出在FreeMarker本身很有可能是前面的环节断了——比如DispatcherServlet根本没有把这个请求路由到Controller或者路由到了但Controller返回的东西不被视图解析器认可。理解这一点很重要。很多人一遇到404就盯着FreeMarker配置死磕改了无数次后缀都无效实际上问题根本不在那边。2.2 自动配置生效的前置条件Spring Boot的自动化配置是有条件的。FreeMarker相关配置类叫FreeMarkerAutoConfiguration它会在类路径里有FreeMarker依赖时才生效。但还有一个更前置的条件类路径里必须有Spring MVC相关的类。所以在pom.xml里只加spring-boot-starter-freemarker是不够的必须同时有spring-boot-starter-web。我见过一个真实的案例某开发者从某个演示项目里复制了依赖只复制了freemarker启动器结果项目起来后访问页面全是404他一度以为是路径问题。后来打开IDE的依赖树一看web starter根本没引入整个MVC体系都没有怎么可能渲染出页面。另外要注意Spring Boot版本差异。早期的1.x版本和现在的3.x版本自动配置的类名、默认属性名都有变化。如果你在网上搜到一篇老博客照着它的配置改很可能发现某个属性名已经不生效了。排查的时候多看一眼自己项目用的Spring Boot大版本别被旧资料带偏。2.3 视图解析器到底如何定位模板视图解析器的定位逻辑用一句话概括拼接。假设Controller返回的逻辑视图名是list配置文件里设置了模板加载路径为classpath:/templates/后缀为.ftl那么视图解析器最终要找的文件路径就是classpath:/templates/list.ftl。这个拼接过程还会经过前缀设置比如设置了spring.mvc.view.prefix/pages/那就要在模板路径后面再拼一层pages目录。这里的坑在于很多人会同时设置Spring MVC的视图配置和FreeMarker的视图配置。Spring Boot对FreeMarker有专门的命名空间spring.freemarker.*其中spring.freemarker.template-loader-path负责设置模板加载根路径spring.freemarker.suffix负责设置后缀而spring.mvc.view.prefix和spring.mvc.view.suffix是通用视图解析器的配置。两者如果混用很容易出现路径重复拼接或者后缀冲突。还有一点容易忽略FreeMarker默认支持的文件后缀不止.ftl如果你引入的是新版本依赖可能还会识别.ftlh。所以Controller里返回视图名时要严格遵守配置里的后缀名——配置文件写的是.ftl你就返回xxx.ftl对应的逻辑视图名。不写后缀让视图解析器帮你拼是最安全的方式。3. 实操过程与核心配置实现3.1 搭建最小的可验证演示项目X不讲空理论直接搭一个最小项目来验证。我建议你跟着动手复现一遍问题再解决比单纯看答案印象深得多。先创建一个空的Spring Boot项目不需要用什么框架脚手架直接用你熟悉的构建工具建一个Maven项目就行。pom.xml里最少要有这两个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-freemarker/artifactId /dependency注意如果你用的是Spring Boot 3.x确保JDK版本在17以上。之前有用户反馈JDK版本太低导致项目起不来那又是另一类404了——服务根本没启动访问任何路径都是404。接下来准备目录结构。按照默认约定模板放在src/main/resources/templates/下。我建两个模板文件一个是首页index.ftl一个是列表页list.ftl。这样可以从首页跳转到列表页完整复现“页面跳转404”的场景。3.2 三行配置文件解决路径定位在application.properties里写上这三行这是我认为最稳的FreeMarker基础配置spring.freemarker.template-loader-pathclasspath:/templates/ spring.freemarker.suffix.ftl spring.freemarker.cachefalse第一行指定模板加载路径告诉视图解析器去哪里找.ftl文件。第二行指定后缀这样Controller里返回index时解析器会自动找index.ftl。第三行关闭模板缓存开发阶段改了模板立刻生效不用反复重启项目。这三行配置已经能解决绝大多数路径定位问题。你可能会问Spring Boot不是有默认配置吗不写也能跑吧对很多情况下不写也能跑因为默认值正好就是classpath:/templates/和.ftl。但我建议显式写出来原因有两点一是防止别人的自定义配置覆盖了默认值二是让项目里每个成员都能一眼看到正确的路径约定。有一个细节值得注意template-loader-path里那个classpath:前缀一定要写。有人图省事写成/templates/结果运行时解析的是文件系统路径而不是classpath直接404。这两个写法在开发环境可能表现一致打包运行后就有差别了。3.3 Controller写法与页面跳转细节Controller的写法是页面跳转404的高发区这里我把正确写法拆开讲。Controller public class PageController { GetMapping(/index) public String index() { return index; } GetMapping(/list) public String list(Model model) { model.addAttribute(items, java.util.List.of(A, B, C)); return list; } }注意类上用的是Controller不是RestController。如果用了RestControllerSpring会把返回的index当作字符串响应体重接输出到浏览器地址栏访问/index时你看到的不是模板渲染结果而是纯文本index。这种情况虽然不叫404但用户感受和404差不多——页面没跳转内容也不对。另外方法里不要自己拼路径。有人习惯写成return /templates/index或者return index.ftl这会让视图解析器无法正确处理因为解析器会自动拼接模板根路径和后缀。正确做法是只返回逻辑视图名不带目录、不带后缀。如果要演示页面跳转在index.ftl里加一个普通链接a href/list查看列表/a点击这个链接浏览器请求/list后端Controller返回list视图解析器去classpath:/templates/list.ftl找到模板并渲染。这一整个链路如果能走通说明页面跳转基本没有问题了。我在实际项目里还喜欢加一个全局异常提示页面当模板确实找不到时能看到具体的错误信息而不是泛泛的404页面。这个属于加分项但排查时特别好用。4. 常见问题与排查技巧实录4.1 五步定位法我把遇到页面跳转404时的排查顺序整理成一套固定流程照着走基本上能定位绝大多数问题。第一步先看控制台启动日志。项目启动时有没有FreeMarker相关的初始化信息如果日志里压根看不到FreeMarker说明依赖没生效先回去检查pom.xml。这里有个细节Spring Boot日志默认不打印自动配置报告你可以临时在配置文件里加上debugtrue启动后看到Positive matches里包含FreeMarkerAutoConfiguration就说明FreeMarker自动配置已经生效。第二步检查浏览器地址栏里的URL和Controller的GetMapping路径是否完全匹配。包括大小写、末尾斜杠。有人喜欢在URL末尾加斜杠但Controller映射里没有直接404。第三步检查Controller返回的视图名和文件系统里的模板文件是否对得上。打开src/main/resources/templates/目录确认文件名和后缀别把文件名写成list.ftl.html这种诡异的东西。第四步检查配置文件里的template-loader-path和后缀是否和实际目录一致。改配置后记得重启项目因为ConfigurationProperties配置不会热更新。第五步用最简单的请求排除干扰。不要从页面点击跳转直接在地址栏输入完整URL访问比如http://localhost:8080/list。这样能排除前端路由、拦截器等方面的干扰直接看后端到底能不能正确处理这个请求。4.2 典型问题速查表我把常见的现象、原因和处理方法整理成一张表排查时可以直接对照。现象可能原因处理方法访问任意URL都显示Whitelabel Error Page项目缺少spring-boot-starter-web依赖MVC未启用在pom.xml添加web启动器并刷新项目控制台报Template not found模板位置不对或后缀配置错误检查template-loader-path和suffix确认.ftl文件名匹配页面显示纯文本index而不是渲染后的页面Controller误用RestController改成Controller或移除类上的ResponseBody页面能打开但点击跳转后404模板链接路径与GetMapping路径不符检查模板里的href地址和后端路由是否一致修改模板后不生效始终渲染旧内容FreeMarker模板缓存未关闭设置spring.freemarker.cachefalse开发环境正常打包后404使用文件系统路径而非classpath路径template-loader-path使用classpath:/templates/配置了多个视图解析器导致冲突同时设置了spring.mvc.view和spring.freemarker统一使用spring.freemarker.*配置移除重复项这张表我每次帮人排查问题时都会翻出来对照大部分404都能在里面找到对应项。4.3 我的独家避坑经验最后分享几个常规文档里不会写的经验。第一个是修改配置后一定要清理编译目录。IDE有时候不会把resources下的新文件同步到target/classes里导致运行时用的还是旧的模板文件。我遇到过在IDE里怎么改都404最后手动删掉target目录重新编译才解决。这种问题最坑因为代码看着全对实际运行的根本不是最新版本。第二个经验是FreeMarker和静态资源的关系。templates/目录下放的是需要渲染的模板static/目录下放的是JS、CSS、图片等静态资源。不要把模板文件丢到static里Spring Boot把它当静态资源处理不会执行FreeMarker表达式。这会引起一种很误导人的现象页面能访问到但里面的变量全都没替换。第三个经验是依赖版本冲突引发的奇怪问题。有用户引入了一个老版本的freemarker依赖结果Spring Boot自动配置生成的后缀是.ftl但是老版本默认找.ftlh怎么调都不对。建议优先使用spring-boot-starter-freemarker统一管理版本不要自己额外引入单独依赖。我个人排查这类问题的习惯是打开debug日志看上几秒钟然后直接去target/classes目录看有没有模板文件。真的这个顺序帮我绕过很多假问题。最后再分享一个小技巧如果你确认配置和代码都对还是404试着把浏览器缓存清一下。浏览器会把404响应缓存住尤其是页面里做了跳转的你可能以为后端有问题其实后端早就修好了只是浏览器还在用旧缓存。先换个浏览器无痕模式测一下很多时候问题当场就消失了。