Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示

Spring MVC + Thymeleaf 模板解析失败:从 500 错误到页面正常显示

问题现象

Spring MVC 项目访问 http://localhost:8080/usermgt/ 时,页面返回 500 错误,浏览器显示:

复制代码
HTTP状态 500 - 内部服务器错误
消息 Request processing failed: 
org.thymeleaf.exceptions.TemplateInputException: 
An error happened during template parsing 
(template: "ServletContext resource [/WEB-INF/thymeleaf/index.html]")

日志里重复出现:

复制代码
Mapped to ParameterizableViewController [view="index"]
Exception processing template "index": 
Could not open ServletContext resource [/WEB-INF/thymeleaf/index.html]

错误定位

日志其实已经说得很清楚:请求到达了 Controller,视图名 index 也成功返回,问题出在 Thymeleaf 去找模板文件时找不到。

关键行:

复制代码
Could not open ServletContext resource [/WEB-INF/thymeleaf/index.html]

Thymeleaf 拼出来的路径是 /WEB-INF/thymeleaf/index.html,但项目里这个文件不存在,所以抛出 FileNotFoundException,最终返回 500。

根本原因

Thymeleaf 的 SpringResourceTemplateResolver 按以下规则拼接模板路径:

复制代码
prefix + viewName + suffix

当前配置是:

xml 复制代码
<bean id="templateResolver" 
      class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/thymeleaf/"/>
    <property name="suffix" value=".html"/>
</bean>

Controller 返回 "index",拼出来就是:

复制代码
/WEB-INF/thymeleaf/index.html

而实际模板文件放在 src/main/webapp/WEB-INF/templates/index.html,路径不一致,所以找不到。

解决方式

有两种改法,二选一。

方式一:改配置,让 prefix 指向模板实际所在目录

如果模板在 WEB-INF/templates/ 下,把 prefix 改成:

xml 复制代码
<property name="prefix" value="/WEB-INF/templates/"/>

这样拼接结果变成 /WEB-INF/templates/index.html,和实际文件对上。

方式二:改文件位置,把模板放到 prefix 指定的目录

如果配置保持 /WEB-INF/thymeleaf/,就把模板文件放到:

复制代码
src/main/webapp/WEB-INF/thymeleaf/index.html

配置不改,文件跟着配置走。

两种方式效果一样,关键是配置里的 prefix 和文件实际位置必须一致。

排查过程中容易忽略的几个点

1. 确认当前生效的是哪个配置文件

日志里出现:

复制代码
Loaded 33 bean definitions from class path resource [springmvc.xml]

说明实际加载的是 classpath:springmvc.xml,不是默认的 /WEB-INF/springmvc-servlet.xml。

DispatcherServlet 的默认规则是加载 /WEB-INF/<servlet-name>-servlet.xml。如果 web.xml 中配置了:

xml 复制代码
<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:springmvc.xml</param-value>
</init-param>

那就改 springmvc.xml,改 springmvc-servlet.xml 是无效的。

2. 确认 target 目录里的文件是最新的

IDEA 部署时用的不是 src/main/webapp,而是 target 下的构建产物。修改模板或 XML 后,要确认:

复制代码
target/项目名/WEB-INF/templates/index.html
target/项目名/WEB-INF/classes/springmvc.xml

里面是最新内容。如果没有,执行 Build → Rebuild Project 后重新部署。

3. 确认访问路径和 Application context

日志里请求的是:

复制代码
GET "/usermgt/"

说明当前部署的 Application context 是 /usermgt,访问地址应该是:

复制代码
http://localhost:8080/usermgt/

如果上下文路径改过,访问路径也要跟着改,否则请求到不了 DispatcherServlet,日志里不会有 Initializing Servlet。

修正后的关键配置

xml 复制代码
<bean id="templateResolver" 
      class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/WEB-INF/templates/"/>
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="cacheable" value="false"/>
</bean>

<bean id="templateEngine" 
      class="org.thymeleaf.spring6.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver"/>
</bean>

<bean id="viewResolver" 
      class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine"/>
    <property name="characterEncoding" value="UTF-8"/>
</bean>

对应的文件结构:

复制代码
src/main/webapp/WEB-INF/
├── templates/
│   ├── index.html
│   └── user_list.html
├── springmvc.xml
└── web.xml

重启 Tomcat,访问 http://localhost:8080/usermgt/,页面正常显示。

排查流程总结

遇到 Thymeleaf 模板找不到的 500 错误,按这个顺序排查:

步骤 检查内容
1 看日志里 Could not open 后面的完整路径
2 对比这个路径和项目里模板文件的实际位置
3 如果路径不匹配,改 prefix 或移动文件
4 确认当前生效的是哪个配置文件(看 Loaded ... from ... 那行)
5 确认 target 目录里的文件和配置是最新的
6 确认访问路径和 Application context 一致

总结

TemplateInputException 和 FileNotFoundException 本质上都是路径问题。Thymeleaf 按 prefix + viewName + suffix 拼接模板路径,只要配置和文件位置对齐,问题就能解决。

这次排查的关键是三点:

  1. 看日志里的完整路径,不要只看异常类型。
  2. 确认改的是真正生效的配置文件 ,springmvc.xml 和 springmvc-servlet.xml 只能有一个在起作用。
  3. 确认 target 里的文件是最新的,IDEA 的部署产物不是源码目录。

把这三件事确认清楚,Spring MVC 的模板渲染问题基本都能快速定位。

相关推荐
lgcgkCQ3 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
Wang's Blog4 小时前
Java 项目实战:交友项目 - 用户体系集成总结
java·开发语言·交友
学到老活到老4 小时前
第 24 集:配置文件写了 80,Spring Boot 为什么读到 90?
java·后端·spring
Flynn Liang4 小时前
RAG 答非所问/幻觉怎么破?Spring AI QuestionAnswerAdvisor 召回优化实战(TopK/重排/混合检索)
人工智能·算法·spring·rag·幻觉·springai
tongyiixiaohuang4 小时前
供应商主数据同步实战:从易快报到金蝶云星空的单一策略落地
java·网络·数据库
鱼宵5 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
学长毕设5 小时前
基于SpringBoot的餐厅管理系统-附源码
java·spring boot·后端
刚入门的大一新生5 小时前
Linux-线程互斥
java·开发语言
赵小哥要冲啦6 小时前
社区邻里互助小程序从 0 到 1 复盘:需求拆解与信任机制设计
java·小程序·需求分析