死磕Tomcat系列(6)——Tomcat如何做到热加载和热部署的
死磕Tomcat系列(6)——Tomcat如何做到热加载和热部署的
热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载class,从而升级应用。
通常情况下在开发环境中我们使用的是热加载,因为热加载的实现的方式在Web容器中启动一个后台线程,定期检测相关文件的变化,如果有变化就重新加载类,这个过程不会清空Session。而在生产环境我们一般应用的是热部署,热部署也是在Web应用后台线程定期检测,发现有变化就会重新加载整个Web应用,这种方式更加彻底会清空Session。
热加载
热加载其实我们在开发过程中经常使用,例如我们使用Idea开发时,我们在设置页面可以进行设置,当修改文件时,我们可以选择不重启项目,选择重新加载此文件。而在Tomcat中也能设置,Tomcat默认情况下是不开启热加载的。需要在Tomcat路径下的Context.xml
中配置reloadable
参数来开启这个功能。
<Context reloadable="true"/>
我们演示一下Tomcat是如何热加载的。在webapp下我们新建了一个项目,里面的Servlet文件如下
public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyServlet 在处理 get()请求..."); PrintWriter out = response.getWriter(); response.setContentType("text/html;charset=utf-8"); out.println("<strong>>My Servlet Version1!</strong><br>"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyServlet 在处理 post()请求..."); PrintWriter out = response.getWriter(); response.setContentType("text/html;charset=utf-8"); out.println("<strong>>My Servlet Version1!</strong><br>"); } }
目录结构如下
-webapp -- mywebapp -- WEB-INF -- web.xml -- classes -- MyServlet.class
为了演示Tomcat运行时能修改class文件能够动态加载。我们分为以下三步
- 正常启动Tomcat。输入
http://localhost:8080/mywebapp/myservlet
,观察页面输出 - 在Tomcat启动的情况下修改
MyServlet
文件后覆盖原来的class文件 - 再次观察页面情况。观察页面输出是否修改
下面直接用动态图演示效果,更直观一些。
我们可以看到在Tomcat运行的情况下,直接替换class文件是能够直接生效的。那么Tomcat是如何做到的呢?其实我们可以自己推导一下。
- 所有的class文件都是交由类加载来管理的
- 如果换了class文件是不是只需要更换相应的类加载器重新加载就行
那么接下来我们来验证我们的结论,看一下在Tomcat中是如何实现热加载的。Tomcat要监听class文件是否变化应该是新起了一个线程来观测。那么看到在Context的启动方法中,看到调用了threadStart
的方法。
protected void threadStart() { backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessor(),//要执行的Runnable backgroundProcessorDelay, //第一次执行延迟多久 backgroundProcessorDelay, //之后每次隔多久执行一次 TimeUnit.SECONDS); //时间单位 } }
其中在后台开启周期性的任务,使用了Java提供的ScheduledThreadPoolExecutor
。除了能周期性执行任务以外还有线程池的功能。上面代码中调用了scheduleWithFixedDelay
方法,第一个传入的参数就是要执行的任务。我们接下来看任务类ContainerBackgroundProcessor
是如何实现的。
protected class ContainerBackgroundProcessor implements Runnable { @Override public void run() { // 请注意这里传入的参数是 " 宿主类 " 的实例 processChildren(ContainerBase.this); } protected void processChildren(Container container) { try { //1. 调用当前容器的 backgroundProcess 方法。 container.backgroundProcess(); //2. 遍历所有的子容器,递归调用 processChildren, // 这样当前容器的子孙都会被处理 Container[] children = container.findChildren(); for (int i = 0; i < children.length; i++) { // 这里会判断子容器如果已经启动了后台线程,那么这里就不会启动了 if (children[i].getBackgroundProcessorDelay() <= 0) { processChildren(children[i]); } } } catch (Throwable t) { ... }
上面代码中我们可以知道具体的后台监听代码是在backgroundProcess
方法中实现的。那么我们看Context
容器的backgroundProcess
方法是如何实现的。
public void backgroundProcess() { //WebappLoader 周期性的检查 WEB-INF/classes 和 WEB-INF/lib 目录下的类文件 Loader loader = getLoader(); if (loader != null) { loader.backgroundProcess(); } ............省略 }
进去loader.backgroundProcess();
中我们可以看到
public void backgroundProcess() { //此处判断热加载开关是否开启和监控的文件夹中文件是否有修改 if (reloadable && modified()) { try { Thread.currentThread().setContextClassLoader (WebappLoader.class.getClassLoader()); if (context != null) { //Context重启 context.reload(); } } finally { if (context != null && context.getLoader() != null) { Thread.currentThread().setContextClassLoader (context.getLoader().getClassLoader()); } } } }
我们可以发现Tomcat热加载的步骤
- 如果发现有文件发生变化,热加载开关开启
- 关闭Context容器
- 重启Context容器
在这个过程中,最重要的部分其实就是类加载器了。因为一个Context容器对应一个类加载器。所以在销毁Context容器的时候也连带着将其类加载器一并销毁了。Context在重启的过程中也会创建新的类加载器来加载我们新建的文件。
热部署
如果还是不懂热部署是什么的,下面演示一遍应该就明白了。Tomcat在启动的时候会将其目录下webapp中war包解压后然后封装为一个Context供外部访问。那么热部署就是在程序运行时,如果我们修改了War包中的东西。那么Tomcat就会删除之前的War包解压的文件夹,重新解压新的War包。
我们发现上面动图中在Tomcat运行时,我们修改了War包的信息,它就会将原来的删除然后重新生成一份。
我们从上面的动图中其实就看出了热部署和热加载的区别了。热部署是将文件夹删除然后重新解压包。那么热加载是由Context容器负责的。那么热部署又是由哪个容器负责呢?因为一个文件夹对应一个Context。既然文件夹都删除了,那么肯定不是由Context容器负责了。那么应该就是Context的父容器Host来负责。
我们可以看到Host
容器并没有实现自己的backgroundProcess
方法。那么它是如何监听的呢?既然它没有实现方法,肯定是调用了父类的backgroundProcess
方法。我们可以看到在父类的backgroundProcess
中
@Override public void backgroundProcess() { . ...........省略 fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); }
可以看到周期事件的监听器。而Host
的事件监听器是HostConfig
类的lifecycleEvent
方法
@Override public void lifecycleEvent(LifecycleEvent event) { if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {// 周期事件 check(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {// 开始之前事件 beforeStart(); } else if (event.getType().equals(Lifecycle.START_EVENT)) { // 开始事件 start(); } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { // 结束事件 stop(); } }
我们可以看check
方法
protected void check() { if (host.getAutoDeploy()) { // 检查Host下所有已经部署的web应用 DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]); for (int i = 0; i < apps.length; i++) { if (!isServiced(apps[i].name)) checkResources(apps[i], false); } // 检查Web应用是否有变化 if (host.getUndeployOldVersions()) { checkUndeploy(); } // 执行部署 deployApps(); } }
热部署的步骤其实也可以简化为三步骤
- 检查Host管理下的所有web应用
- 如果原来的Web应用被删除,就将相应Context容器删除
- 如果有新War包放进来,就部署相应的War包
参考文章
往期文章
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何同步多个 git 远程仓库
日常需求 以前源码是托管在 github 的, 现在想要同步托管在 gitee, 一做备份分发, 二方便国内下载使用(网速可观), 三防特色墙... 方式一 使用 gitee 的强制同步 之前在 github 托管了这么一个项目 mirrors-in-china, 后来国内出了 gitee, 那么想着把项目同步一份到 gitee, 方便大家查看... 正巧 gitee 提供强制同步功能, 方便操作... 我还是只用维护 github 那份源码, gitee 这边没忘记的话, 手搓点击下强制同步按钮即可. 但是容易忘记, 造成两边不完全同步. 不过我这个项目本身就非常简单, 这点同步时差完全没大问题, 够用, 并且没有其他任何多余的操作. 方式二 手搓 push 多次 换另一个项目来说, 我之前在 github 托管了这么一个项目 GlobalScanner.Sdk, 应广大小伙伴需求, 希望把项目在国内同步一份, 方便下载/参考/使用. 那么不外乎就是配置多个远程库地址, 多次推送咯, 那么我们先来看看现有远程库的情况: $ git remote --verbose origin gi...
- 下一篇
Soloπ:支付宝开源的Android专项测试工具
1.前言 近年来,随着移动互联网的蓬勃发展,移动测试技术也取得了长足的进步,从早期基于测试脚本的单机自动化,到录制回放、图像识别、云测平台等测试技术贴合实际业务需求深度应用和创新,测试效率从而一次又一次被提升。 本文主要介绍支付宝在移动端上实现的一套无线化、非侵入、免 Root 的 Android 专项测试方案 Soloπ。直接操控手机,即可实现自动化的功能、性能、兼容性、以及稳定性测试等工作。 1.1 移动测试 1.0 时代 移动测试 1.0 时代,也可以称之为探索期。由于厌倦了日复一日的手工操作,如何提升测试效率成为了移动测试领域最重要的课题,在此期间,除了 Monkey、UiAutomator、Instruments 等官方提供的工具,业界还涌现了一批优秀的开源自动化测试工具/框架,在自动化驱动能力的基础之上,不仅可以实现基本功能的验证,还可以结合性能采集方案、遍历算法等实现各类专项测试的自动化。在这个阶段,自动化测试的常见形态是在单机或本地少数几台 PC 上部署测试环境,再利用 Jenkins 等工具实现持续集成。 1.2 移动测试 2.0 时代 伴随着测试技术的持续发展、又得...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)