前言
在做公司的一個(gè)項(xiàng)目阱当,使用springboot加thymeleaf,由于開發(fā)過程中一直是在idea中進(jìn)行糜工,并且頁面顯示良好弊添,因此覺得功能使用是沒有問題的,但是昨天下午在用maven打包之后捌木,例行測(cè)試時(shí)發(fā)現(xiàn)了很奇怪的問題:
部分頁面無法打開油坝,出現(xiàn)了500錯(cuò)誤;另外一些頁面則顯示正常钮莲,于是開始排查問題所在
1.定位錯(cuò)誤原因
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)
根據(jù)打印出來的堆椕庾辏可以看到是在ThymeleafViewResolver
渲染頁面時(shí)發(fā)現(xiàn)無法找到目標(biāo)文件,問題找到了崔拥,但是在idea中又能正常使用极舔,這個(gè)現(xiàn)象很奇怪
2.網(wǎng)上查查有沒有遇到同樣問題的
然后還真找到了一個(gè)類似的問題:
Error resolve template in a jar-stackoverflow
根據(jù)被采納的回答來看,有兩種情況會(huì)導(dǎo)致出現(xiàn)這種問題:
- 返回視圖路徑以
/
開頭链瓦,例如/test/hello
- 在thymeleaf頁面中拆魏,引入的頁面以
/
開頭盯桦,例如:<footer th:replace="/index::footer"></footer>
在將出問題的頁面和正常的頁面對(duì)比后,發(fā)現(xiàn)是第一種情況導(dǎo)致的問題渤刃,將多余的/
刪除拥峦,問題解決
3.為什么多余的/
會(huì)導(dǎo)致jar運(yùn)行出問題而idea模式下沒問題呢?
打開idea卖子,進(jìn)入debug模式略号,在跑出異常的TemplateManager
的parseAndProcess()
方法加上斷點(diǎn),查看程序運(yùn)行堆棧:
classpath-error.jpg
可以看到洋闽,在有
/
條件下玄柠,斷點(diǎn)那一行templateResolution
的resource
屬性全路徑是templates//mgm/reward/batch.html
,這個(gè)文件路徑肯定是沒法找到對(duì)應(yīng)的文件路徑的诫舅。
作為對(duì)比羽利,下圖是正常路徑
classpath.jpg
這里算是找到出問題的緣由了,就是因?yàn)榉祷芈窂讲粚?duì)導(dǎo)致在最后視圖渲染時(shí)由于找不到對(duì)應(yīng)文件刊懈,拋出異常这弧,
resolveTemplate()
方法如圖:exception-reason.jpg
在最后一段,找不到文件的話虚汛,默認(rèn)拋出
TemplateInputException
其實(shí)到這里也只能算是確認(rèn)了jar模式下匾浪,視圖解析異常的原因;那么同樣的代碼為什么idea能正常運(yùn)行不拋出異常呢泽疆?
在前面的stackoverflow有一個(gè)解釋:
reason.jpg
說是因?yàn)閕dea在啟動(dòng)時(shí)户矢,是從文件系統(tǒng)中加載資源,對(duì)于
//
,文件系統(tǒng)是支持這種尋址的殉疼,但是在jar的內(nèi)部資源加載就無法使用這種模式了梯浪,到此原因終于確認(rèn)了。