Springboot项目打包后诡异的页面丢失问题

前言

在做公司的一个项目,使用springboot加thymeleaf,由于开发过程中一直是在idea中进行,并且页面显示良好,因此觉得功能使用是没有问题的,但是昨天下午在用maven打包之后,例行测试时发现了很奇怪的问题:
部分页面无法打开,出现了500错误;另外一些页面则显示正常,于是开始排查问题所在

1.定位错误原因

12:08:26 [http-nio-8084-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/mgm/reward/batch", template might not exist or might not be accessible by any of the configured Template Resolvers] with root cause
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "/mgm/reward/batch", template might not exist or might not be accessible by any of the configured Template Resolvers
       at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:870)
       at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607)
       at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
       at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072)
       at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:354)
       at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:187)
       at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1325)
       at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1069)
       at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1008)
       at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
       at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
       at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
       at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
       at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
       at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
       at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
       at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
       at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
       at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
       at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
       at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
       at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
       at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
       at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
       at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
       at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
       at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
       at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
       at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
       at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
       at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
       at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
       at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
       at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
       at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
       at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
       at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
       at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
       at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
       at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
       at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
       at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
       at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
       at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
       at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
       at java.lang.Thread.run(Unknown Source)

根据打印出来的堆栈可以看到是在ThymeleafViewResolver渲染页面时发现无法找到目标文件,问题找到了,但是在idea中又能正常使用,这个现象很奇怪

2.网上查查有没有遇到同样问题的

然后还真找到了一个类似的问题:
Error resolve template in a jar-stackoverflow
根据被采纳的回答来看,有两种情况会导致出现这种问题:

  • 返回视图路径以/开头,例如 /test/hello
  • 在thymeleaf页面中,引入的页面以/开头,例如:

    在将出问题的页面和正常的页面对比后,发现是第一种情况导致的问题,将多余的/删除,问题解决

3.为什么多余的/会导致jar运行出问题而idea模式下没问题呢?

打开idea,进入debug模式,在跑出异常的TemplateManagerparseAndProcess()方法加上断点,查看程序运行堆栈:

classpath-error.jpg

可以看到,在有/条件下,断点那一行templateResolutionresource属性全路径是templates//mgm/reward/batch.html,这个文件路径肯定是没法找到对应的文件路径的。


作为对比,下图是正常路径

classpath.jpg

这里算是找到出问题的缘由了,就是因为返回路径不对导致在最后视图渲染时由于找不到对应文件,抛出异常,resolveTemplate()方法如图:
exception-reason.jpg

在最后一段,找不到文件的话,默认抛出TemplateInputException


其实到这里也只能算是确认了jar模式下,视图解析异常的原因;那么同样的代码为什么idea能正常运行不抛出异常呢?
在前面的stackoverflow有一个解释:

reason.jpg

说是因为idea在启动时,是从文件系统中加载资源,对于//,文件系统是支持这种寻址的,但是在jar的内部资源加载就无法使用这种模式了,到此原因终于确认了。

你可能感兴趣的:(Springboot项目打包后诡异的页面丢失问题)