spring boot内置tomcat运行JSP报错的解决方案

===============================================

 2023/2/23_第1次修改                       粽先生

 

===============================================

写下这篇记录,是我终于在最近解决了这个困扰了项目长达一年的问题。由于从发现问题到解决,时间跨度较大,也让我更进一步的理解官方为什么不建议使用spring boot运行JSP项目,所以我抽空写了这篇记录,希望能帮到也被这个问题困扰的路人。

 

由于项目需要,我接手的一个已经完成了一半的JSP项目,根据后续提供的需求完成新功能的开发和优化工作。由于该JSP项目接手时已经完成了spring boot的改造,所以从一开始就是通过spring boot打jar包、做部署的。

 

可能很多java开发和我一样,没有经历过war包直接开始就是spring boot打jar包,所以这里我也简单的描述下war包、jar包的差异。

java传统的web应用通常通过打war包的方式,然后将war包部署到tomcat中。而spring boot应用使用嵌入式的servlet容器,spring boot应用会在刷新上下文时启动内置的tomcat实例。

 


一、问题描述

言归正传,我接手的这个JSP项目在正常的开发调试、上线部署后刚开始运行时功能都正常。但是运行一段时间后,客户反馈有些业务会出现闪退到登录页面的问题。经过日志和测试环境的跟踪,发现这个问题能够经常复现,有时也会出现明明前几天还能正常访问的页面,没过几天就报错:

Unable to complie class for JSP:

An error occurred at line: XXXX

org.apache.jsp.tag.web.sys.message_tag cannot be resolved to a type

 

期间也从很多渠道找过资料,发现类似的报错信息都是因为开发人员的代码问题,导致在调试阶段就直接报错。

而我的项目是开发调试阶段功能正常,甚至刚上线运行功能也正常,但是一段时间不定期就会出现这个问题,而通过页面的报错信息和日志内容都只能定位到类似信息。

 

所以在没法定位问题之前,临时通过“报错了就重启一次程序”来解决这个问题。但是由于系统使用的频率变高,这种方案越来越增加运维复杂度,也让我开始花精力开始解决这个问题。

 


二、解决思路

我问了之前项目组的开发人员,没有一个开发遇到过这个问题,同时这个项目也是唯一做了spring boot改造的JSP项目。

 

有个同事说可能是tomcat插件不兼容的问题

这个理由我觉得说不通,因为该项目是2.X的spring boot,启动时打印的日志也很清楚,内置的tomcat版本是9,如果是不兼容,从调试阶段应该就能发现这个问题,而我在调试阶段功能都是正常的。

 

难道JSP页面在运行一段时间后,又重新编译了?

明明前几天还正常访问的页面,过几天就报错了,于是我思考是不是JSP运行一段时间后会重新编译?但是搜索了很长时间,既没有找到能解释JSP页面文件编译原理的文章,也没有找到JSP或者tomcat重新编译的内容。

 

tomcat没异常,难道是JVM的GC回收有问题?

这个方向我也没有特定的思路,只是看到群里别的项目好像GC回收有问题,但是这个项目我经过反复检查,感觉GC方面也看不出问题来。

 

到此,这个问题就停摆了很久。。

 


三、原因分析

转机是我无意之中在找其他资料时,翻到了spring boot在github上有人提了这么一个issue(https://github.com/spring-projects/spring-boot/issues/5009):

Danger of creating embedded tomcat work directory in temporary directory on CentOS 6+

 

标题让我一下有了思路,难道是内置tomcat缓存的目录有问题?

 

结合分析#8459“Reconsider use of /tmp by TomcatEmbeddedServletContainerFactory”(https://github.com/spring-projects/spring-boot/issues/8459)、#25890“Default for server.tomcat.basedir shoudn't be /tmphttps://github.com/spring-projects/spring-boot/issues/25890)的issue评论,问题终于定位到了,原因就出在spring boot内置tomcat时默认的目录/tmp上

 

这个其实和jar包运行在linux的机制有关,因为测试和生产环境都是直接将jar包运行在centos7上(实际的场景这个项目不会在docker、或windows server里去运行),由于内置tomcat默认的缓存路径是/tmp,包括我们做上传文件接口开发时,其实上传的文件最先是缓存在这个目录下,而JSP项目中JSP文件、tag文件等,都会在被访问的时候缓存到/tmp目录中。

 

然而linux的文件系统遵循FHS(Filesystem Hierarchy Standard)标准,其tmpwatch服务,默认情况下会删除/tmp目录中访问时间超过 10 天的文件,这就导致一段时间后tmpwatch删除了JSP项目缓存的文件。

由于spring boot的机制,针对tag文件不会重复将文件缓存到tomcat的缓存目录下,从而导致JSP页面虽然存在,但是由于使用了tag文件,引起报错。

 

为什么新版本的spring boot也不更换tomcat的默认路径?

从评论来说,开发者也不考虑在新的spring boot中更换内置tomcat的默认路径,因为这违反了“约定优于配置”的理念。

 


四、解决方案

既然原因找到了,那么解决的思路也简单,就是设置内置tomcat路径不要指向/tmp。

 

在spring boot的配置中,增加下面的配置:

server:
    tomcat:
        basedir: /home/service_tmp #这里只是例子选择在该目录,实际要根据服务器磁盘情况而定

 

我没有试验过新jar包中文件有更新,是否会替换内置tomcat目录下的文件,所以我也建议在新部署的时候最好将缓存删了再启动新jar包。

 


五、事后思考

如果我是spring boot的开发者,我该怎么解决这个问题?

首先肯定不能假设用户会去停止tmpwatch服务,因为tmpwatch定期清理缓存目录的操作本身就是正常的功能,试想如果没有它清理缓存,而应用不断写缓存文件,最后的结果是难以想象的。

 

其次,我也同意“约定优于配置”的理念。既然内置tomcat的默认路径已经是/tmp,如果新版本的spring对该路径做改动,无疑又要让使用者多背一个知识点,解决了旧坑又埋了新坑。

 

目前我只能想到优化方向,就是除了JSP文件外,对于tag文件如果缓存文件被删了,就重新编译生成新的缓存文件。但是在我看来JSP就是个日落西山的技术,是否值得为了解决这么个问题去动用开发成本,本身也是项目管理要做的考量。

 

最后总结:

1. 尽可能的不要再去开发新的JSP项目,前端页面的调试特别心累不说,花大气力学JSP是否能有饭吃还是未知数。

2. 如果就是轮到接手一个JSP项目,还是按照官方的描述,尽量避免使用spring boot运行。

3. 如果非要使用spring boot运行JSP项目,请记得修改内置tomcat的路径。

 

 

 

posted @ 2023-02-23 00:23  粽先生  阅读(834)  评论(0编辑  收藏  举报