为了让Tomcat支持热替换,我直接修改了他的源码。

1,082 阅读6分钟

前言

又是一个周日,贫穷的我依然使用电源适配器来暖手,所以,这章来我们聊聊"热"这个问题。

啊啊啊啊啊啊.....

不知道各位网友是怎么解决修改代码后,不重启立即生效对的,如果在Spring Boot项目中,可能有部分人会加入spring-boot-devtools这个依赖,当代码修改后,Spring Boot项目会自动重启,注意是重启项目,当然还有些坑,比如不生效,或者改完代码后过好长一段时间才生效,这样还不如手动点一下重启按钮呢,再有就是,如果配置了Tomcat,以外置Tomcat启动Spring Boot项目的话,那么这种办法可能一点用没有(经过我测试)。

如果项目的启动时间不是很长,那么还可以忍受,但是你能忍受十几秒或者更长的时间吗,可能已经开始口吐芬芳了,那么在这种情况下,有没有办法来解决下这个,当时是有的了,我们可以以debug方式启动项目,然后修改代码后手动点击Hot swap classes,也可以做到不重启项目,更新class,但是不好处就在于是以debug方式启动。

那我们在换一种办法吧,毕竟条条大路通罗马,经过不断的研究,最后我决定了一个方案,也就是改Tomcat源码......好吧,可能还有一些IDEA插件,不过我没有去尝试,因为传说是收费的,所以最后花了些时间,改动了一下Tomcat源码,让他从根本上支持此功能。

注意这里就不是热加载问题了,我们也暂不讨论,Tomcat支持的热加载是不需要重启Tomcat,但是需要重启项目来对class更新,开发阶段修改代码后重启是非常麻烦的,所以,我们讨论热替换,也就是不重启项目的情况下,对修改的代码,让其在1秒内生效,怎么样,听起来是不是很激动?

没错,至少我是的,但是如果你启动Spring Boot项目的方式是从main方法,那我就没办法了,只要你肯在IDEA配置一个Tomcat,让其跑在这个Tomcat里,那么一切都好说,如下图。

这种启动的原理是IDEA会在/user/.IntelliJIdea2019.3/system/tomcat下创建一个Unnamed_项目名的文件夹,里面会有一些Tomcat相关的配置文件,如下图:

他还会生成一个jmx的账号和密码,但是每次启动都会重新生成,可以使用jconsole工具进行连接调试,conf下面就是Tomcat启动时需要的文件,在conf/Catalina/localhost目录下会有一个xml文件,里面通过Context标记配置了项目信息,他的docBase属性指向了class路径。

如果你熟悉Tomcat,那么这些配置应该都很熟悉,接下来IDEA要做的就是修改catalina.base路径,让其指向这个文件夹即可,这样Tomcat原本webapps下的应用就会失效,conf下配置的信息也会失效。

细心的你应该会发现,Tomcat启动的时候就会打印出这些信息。

10-Jan-2021 14:04:28.452 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Server.服务器版本:     Apache Tomcat/9.0.33
10-Jan-2021 14:04:28.456 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 服务器构建:            Mar 11 2020 09:31:38 UTC
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 服务器版本号(:9.0.33.0
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log OS.版本:               4.15.0-30deepin-generic
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 架构:                  amd64
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Java 环境变量:         /home/HouXinLin/apps/java/jdk/jdk1.8.0_241/jre
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log JVM 版本:              1.8.0_241-b07
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log JVM.供应商:            Oracle Corporation
10-Jan-2021 14:04:28.457 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE:         /home/hxl/.IntelliJIdea2019.3/system/tomcat/Unnamed_auto-signin
10-Jan-2021 14:04:28.458 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME:         /home/HouXinLin/apps/tomcat/tomcat2/apache-tomcat-9.0.33
10-Jan-2021 14:04:28.460 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/home/hxl/.IntelliJIdea2019.3/system/tomcat/Unnamed_auto-signin/conf/logging.properties
10-Jan-2021 14:04:28.460 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager

他的原理简单的介绍了一下,下面该我们的Tomcat上场了,先放个视频。

可以看到,1秒内完成了实时更新,但是目前只能做到修改方法体后生效,如果增加或者删除了方法,那么将无法更新,只能重启。

好处就是快,很快,非常快,非常非常快,session也不会丢失,什么都不会丢失!

原理解密

幸好在以前研究过Tomcat源码,不然可能都无法实现,要想要了解原理,就得需要掌握Tomcat项目的启动流程,以及最重要的Web应用类加载器,这是关键,Tomcat会为每个应用生成独立的类加载器,叫做ParallelWebappClassLoader,他是在StandardContext的启动方法中被实例化的,每个StandardContext就代表一个项目应用。

接下来就是就是最最最最主角,Java Agent,在JDK5的时候引入了一个NB的包叫java.lang.Instrument,他可以在运行的时候动态修改系统的class。

关于他的技术,留在下章中好好总结,下面是如何使用。

使用

下载Tomcat

我是基于Tomcat9来构建的,由于需要jdk/lib下的tools.jar,所以打包后体积会加上tools.jar的大小(17M),使用的方式和原版Tomcat无任何区别。最低的JDK版本应该需要8。

这个Tomcat不建议线上运行,适合开发调试阶段,线上的话需要使用官方的版本。

链接:houxinlin.com/apache-tomc…

接着回到IDEA中,依次点击配置Tomcat,然后选择Tomcat,一定要选我的这个。剩下的步骤就不说了,网上一大把。

启动后会有如下日志打印。

10-Jan-2021 14:21:00.731 信息 [Attach Listener] com.hxl.Agent.agentmain Tomcat 热替换启动
10-Jan-2021 14:21:00.754 信息 [Attach Listener] com.hxl.AsmRedefine.initClassLoader 类加载器初始化完成,共发现[1]个
10-Jan-2021 14:21:00.755 信息 [Attach Listener] com.hxl.AsmRedefine.addItem 映射[/ROOT] To [/home/HouXinLin/projects/java/Idea/Java-Project/auto-signin/target/classes]

如果有什么问题,可以留言评价区。

更新

评论区有个小伙伴说了jrebel这个插件,试了下,还挺好用,也可以对新增的方法进行替换,日后我也会对这个Tomcat新增这个功能