在我写一个社区项目的过程中,运行 SpringBoot 后访问 index.html 页面,浏览器页面出现 500 错误,且 IDEA 控制台出现了如下错误信息:
2021-05-03 20:31:40.083 ERROR 9080 --- [nio-8080-exec-1] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-1] Exception processing template "/index": An error happened during template parsing (template: "class path resource [templates//index.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates//index.html]")
......
此时我的项目环境是 SpringBoot + Thymeleaf。
下面部分都是我分析的过程,如果读者不感兴趣的话,可以直接跳到第三部分。
出错时的控制层对应方法如下:
// 获取首页
@GetMapping(path = "/index")
public String getIndexPage(Model model){
// 1. 查询首页帖子
List<DiscussPost> list = discussPostService.selectDiscussPosts(0, 0, 10);
// 2.将每个用户信息以及讨论帖存放到一个 map 集合中
// 然后把 map 集合存放到一个 list 集合中,因为首页肯定有很多用户以及讨论帖
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null){
for (DiscussPost post : list){
// 将每个用户的讨论帖存放到一个 map 集合中。user 为 key,讨论帖为 val
Map<String, Object> map = new HashMap<>();
// 保存讨论帖
map.put("post", post);
// 保存用户信息
User user = userService.findUserById(post.getUserId());
map.put("user", user);
// 加入到 list 集合中
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "/index.html";
}
刚开始出现这个问题的时候,我看到报错内容有 templates//index.html
,我首先想到的是前端页面路径问题。我们知道,对于 SpringMVC 来说,解析路径的配置信息在 WebMvcAutoConfiguration
类中,那么对于 thymeleaf 模板引擎,对应的配置类自然应该是 ThymeleafAutoConfiguration
,在此类中有一个静态内部类 DefaultTemplateResolverConfiguration
,从名字就可以看出来,这个类用于记录模板解析的配置信息,在这个类中有如下方法:
@Bean
SpringResourceTemplateResolver defaultTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(this.applicationContext);
// 设置前缀
resolver.setPrefix(this.properties.getPrefix());
// 设置后缀
resolver.setSuffix(this.properties.getSuffix());
resolver.setTemplateMode(this.properties.getMode());
if (this.properties.getEncoding() != null) {
resolver.setCharacterEncoding(this.properties.getEncoding().name());
}
resolver.setCacheable(this.properties.isCache());
Integer order = this.properties.getTemplateResolverOrder();
if (order != null) {
resolver.setOrder(order);
}
resolver.setCheckExistence(this.properties.isCheckTemplate());
return resolver;
}
在这个方法中,我们可以看到为我们的返回值设置了路径前后缀,而默认的前后缀如下:
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
到这里貌似就是 “柳暗花明” 了,是不是因为返回值是/index.html
,而不是 index
引发的路径错误问题?
很遗憾,答案并不是这样。
再回头看看上面的 defaultTemplateResolver()
方法的第 8 行:
resolver.setSuffix(this.properties.getSuffix());
其中 setSuffix()
函数具体如下:
/**
*
* Sets a new (optional) suffix to be added to all template names in order
* to convert template names into resource names.
*
*
* Note that this suffix may not be applied to the template name if the template name
* already ends in a known file name suffix: {@code .html}, {@code .htm}, {@code .xhtml},
* {@code .xml}, {@code .js}, {@code .json},
* {@code .css}, {@code .rss}, {@code .atom}, {@code .txt}. If this behaviour needs to be overridden so
* that suffix is always applied, the {@link #setForceSuffix(boolean)} will need to be set.
*
*
* @param suffix the suffix to be set.
*/
public final void setSuffix(final String suffix) {
this.suffix = suffix;
}
在第 8 行和第 9 行的注释中已经明确说了:如果模板名称(也就是控制层方法返回值)中以 .html 为后缀结尾,则这个后缀不会生效。
也就是说,控制层方法直接返回 index.html
也不会出错,因为不会对其进行后缀拼接。至于在 index.html
前面加上一个 /
变成了 /index.html
,也并不会影响路径访问。不信的话可以在浏览器中输入 https://www.baidu.com//index.html 也是可以照样访问百度的,应该是浏览器对分隔符做了处理。
至此,虽然没有排除出问题所在,但是起码学到了一点:在控制层方法中,返回的模板名称可以有文件后缀,也可以有 /
前缀。即如果要跳转到 index.html
页面,返回值可以是 /index.html
,也可以是 index.html
,也可以是 index
。
经过查阅一些资料,算是找到了问题的根源。
一般出现这种错误,首先考虑的应该是前端页面出了一些问题,常见的问题可以分为两种。
第一种:在 html 文件头部重复引入 xmlns
。
比如下面这种,在引入 thymeleaf 的同时还引入了别的 xmlns
,这种情况下会出现上述错误。
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:https="http://www.w3.org/1999/xhtml">
解决方案是只保留 thymeleaf,正确写法如下:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
第二种:语法出错。
比如下面这种:
<a href="#">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像">
a>
我这里使用的是 map.user.headerUrl
,实际上我在 POJO 对象中写的属性域是 headUrl
,这里完全是粗心的问题,将其修改过来即可。大家如果排除了第一种可能性的话,一定要好好检查是不是自己的 thymeleaf 语法用错了或者说是对应的属性写错了。
修改完毕之后,重启部署项目,便可以成功访问页面。