|
|
51CTO旗下网站
|
|
移动端

热加载和热部署,没听过?看看 Tomcat 是怎么实现的

通常情况下在开发环境中我们使用的是热加载,因为热加载的实现的方式在Web容器中启动一个后台线程,定期检测相关文件的变化,如果有变化就重新加载类,这个过程不会清空Session。

作者:不学无数的程序员来源:掘金|2020-06-29 21:02

通常情况下在开发环境中我们使用的是热加载,因为热加载的实现的方式在Web容器中启动一个后台线程,定期检测相关文件的变化,如果有变化就重新加载类,这个过程不会清空Session。而在生产环境我们一般应用的是热部署,热部署也是在Web应用后台线程定期检测,发现有变化就会重新加载整个Web应用,这种方式更加彻底会清空Session。

热加载

热加载其实我们在开发过程中经常使用,例如我们使用Idea开发时,我们在设置页面可以进行设置,当修改文件时,我们可以选择不重启项目,选择重新加载此文件。而在Tomcat中也能设置,Tomcat默认情况下是不开启热加载的。需要在Tomcat路径下的Context.xml中配置reloadable参数来开启这个功能。

  1. <Context reloadable="true"/> 

我们演示一下Tomcat是如何热加载的。在webapp下我们新建了一个项目,里面的Servlet文件如下

  1. public class MyServlet extends HttpServlet { 
  2.  
  3.     @Override 
  4.     protected void doGet(HttpServletRequest request, HttpServletResponse response) 
  5.             throws ServletException, IOException { 
  6.  
  7.         System.out.println("MyServlet 在处理 get()请求..."); 
  8.         PrintWriter out = response.getWriter(); 
  9.         response.setContentType("text/html;charset=utf-8"); 
  10.         out.println("<strong>>My Servlet Version1!</strong><br>"); 
  11.     } 
  12.  
  13.     @Override 
  14.     protected void doPost(HttpServletRequest request, HttpServletResponse response) 
  15.             throws ServletException, IOException { 
  16.  
  17.         System.out.println("MyServlet 在处理 post()请求..."); 
  18.         PrintWriter out = response.getWriter(); 
  19.         response.setContentType("text/html;charset=utf-8"); 
  20.         out.println("<strong>>My Servlet Version1!</strong><br>"); 
  21.     } 
  22.  

目录结构如下

  1. -webapp 
  2.     -- mywebapp 
  3.         -- WEB-INF 
  4.             -- web.xml 
  5.             -- classes 
  6.                 -- MyServlet.class 

为了演示Tomcat运行时能修改class文件能够动态加载。我们分为以下三步

  1. 正常启动Tomcat。输入http://localhost:8080/mywebapp/myservlet,观察页面输出
  2. 在Tomcat启动的情况下修改MyServlet文件后覆盖原来的class文件
  3. 再次观察页面情况。观察页面输出是否修改

下面直接用动态图演示效果,更直观一些。

我们可以看到在Tomcat运行的情况下,直接替换class文件是能够直接生效的。那么Tomcat是如何做到的呢?其实我们可以自己推导一下。

所有的class文件都是交由类加载来管理的

如果换了class文件是不是只需要更换相应的类加载器重新加载就行

那么接下来我们来验证我们的结论,看一下在Tomcat中是如何实现热加载的。Tomcat要监听class文件是否变化应该是新起了一个线程来观测。那么看到在Context的启动方法中,看到调用了threadStart的方法。

  1. protected void threadStart() { 
  2.         backgroundProcessorFuture = Container.getService(this).getServer().getUtilityExecutor() 
  3.                 .scheduleWithFixedDelay(new ContainerBackgroundProcessor(),//要执行的Runnable 
  4.                         backgroundProcessorDelay, //第一次执行延迟多久 
  5.                         backgroundProcessorDelay, //之后每次隔多久执行一次 
  6.                         TimeUnit.SECONDS); //时间单位 
  7.     } 

其中在后台开启周期性的任务,使用了Java提供的

ScheduledThreadPoolExecutor。除了能周期性执行任务以外还有线程池的功能。上面代码中调用了scheduleWithFixedDelay方法,第一个传入的参数就是要执行的任务。我们接下来看任务类

ContainerBackgroundProcessor是如何实现的。

  1. protected class ContainerBackgroundProcessor implements Runnable { 
  2.  
  3.     @Override 
  4.     public void run() { 
  5.         // 请注意这里传入的参数是 " 宿主类 " 的实例 
  6.         processChildren(ContainerBase.this); 
  7.     } 
  8.  
  9.     protected void processChildren(Container container) { 
  10.         try { 
  11.             //1. 调用当前容器的 backgroundProcess 方法。 
  12.             container.backgroundProcess(); 
  13.              
  14.             //2. 遍历所有的子容器,递归调用 processChildren, 
  15.             // 这样当前容器的子孙都会被处理             
  16.             Container[] children = container.findChildren(); 
  17.             for (int i = 0; i < children.length; i++) { 
  18.             // 这里会判断子容器如果已经启动了后台线程,那么这里就不会启动了 
  19.                 if (children[i].getBackgroundProcessorDelay() <= 0) { 
  20.                     processChildren(children[i]); 
  21.                 } 
  22.             } 
  23.         } catch (Throwable t) { ... } 

上面代码中我们可以知道具体的后台监听代码是在backgroundProcess方法中实现的。那么我们看Context容器的backgroundProcess方法是如何实现的。

  1. public void backgroundProcess() { 
  2.  
  3.     //WebappLoader 周期性的检查 WEB-INF/classes 和 WEB-INF/lib 目录下的类文件 
  4.     Loader loader = getLoader(); 
  5.     if (loader != null) { 
  6.         loader.backgroundProcess();         
  7.     } 
  8.     ............省略 

进去loader.backgroundProcess();中我们可以看到

  1. public void backgroundProcess() { 
  2.     //此处判断热加载开关是否开启和监控的文件夹中文件是否有修改 
  3.     if (reloadable && modified()) { 
  4.         try { 
  5.             Thread.currentThread().setContextClassLoader 
  6.                 (WebappLoader.class.getClassLoader()); 
  7.             if (context != null) { 
  8.                 //Context重启 
  9.                 context.reload(); 
  10.             } 
  11.         } finally { 
  12.             if (context != null && context.getLoader() != null) { 
  13.                 Thread.currentThread().setContextClassLoader 
  14.                     (context.getLoader().getClassLoader()); 
  15.             } 
  16.         } 
  17.     } 

我们可以发现Tomcat热加载的步骤

  1. 如果发现有文件发生变化,热加载开关开启
  2. 关闭Context容器
  3. 重启Context容器

在这个过程中,最重要的部分其实就是类加载器了。因为一个Context容器对应一个类加载器。所以在销毁Context容器的时候也连带着将其类加载器一并销毁了。Context在重启的过程中也会创建新的类加载器来加载我们新建的文件。

热部署

如果还是不懂热部署是什么的,下面演示一遍应该就明白了。Tomcat在启动的时候会将其目录下webapp中war包解压后然后封装为一个Context供外部访问。那么热部署就是在程序运行时,如果我们修改了War包中的东西。那么Tomcat就会删除之前的War包解压的文件夹,重新解压新的War包。

我们发现上面动图中在Tomcat运行时,我们修改了War包的信息,它就会将原来的删除然后重新生成一份。

我们从上面的动图中其实就看出了热部署和热加载的区别了。热部署是将文件夹删除然后重新解压包。那么热加载是由Context容器负责的。那么热部署又是由哪个容器负责呢?因为一个文件夹对应一个Context。既然文件夹都删除了,那么肯定不是由Context容器负责了。那么应该就是Context的父容器Host来负责。

我们可以看到Host容器并没有实现自己的backgroundProcess方法。那么它是如何监听的呢?既然它没有实现方法,肯定是调用了父类的backgroundProcess方法。我们可以看到在父类的backgroundProcess中

  1. @Override 
  2.     public void backgroundProcess() { 
  3.             .   ...........省略 
  4.         fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null); 
  5.     } 

可以看到周期事件的监听器。而Host的事件监听器是HostConfig类的lifecycleEvent方法

  1. @Override 
  2.     public void lifecycleEvent(LifecycleEvent event) { 
  3.         if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {// 周期事件 
  4.             check(); 
  5.         } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {// 开始之前事件 
  6.             beforeStart(); 
  7.         } else if (event.getType().equals(Lifecycle.START_EVENT)) { // 开始事件 
  8.             start(); 
  9.         } else if (event.getType().equals(Lifecycle.STOP_EVENT)) { // 结束事件 
  10.             stop(); 
  11.         } 
  12.     } 

我们可以看check方法

  1. protected void check() { 
  2.  
  3.     if (host.getAutoDeploy()) { 
  4.         // 检查Host下所有已经部署的web应用 
  5.         DeployedApplication[] apps = 
  6.             deployed.values().toArray(new DeployedApplication[0]); 
  7.         for (int i = 0; i < apps.length; i++) { 
  8.             if (!isServiced(apps[i].name)) 
  9.                 checkResources(apps[i], false); 
  10.         } 
  11.  
  12.         // 检查Web应用是否有变化 
  13.         if (host.getUndeployOldVersions()) { 
  14.             checkUndeploy(); 
  15.         } 
  16.  
  17.         // 执行部署 
  18.         deployApps(); 
  19.     } 

热部署的步骤其实也可以简化为三步骤

  1. 检查Host管理下的所有web应用
  2. 如果原来的Web应用被删除,就将相应Context容器删除
  3. 如果有新War包放进来,就部署相应的War包

作者:不学无数的程序员

链接:https://juejin.im/post/5d2709d7e51d4510664d1774

来源:掘金

【编辑推荐】

  1. 解析Tomcat内部结构和请求过程
  2. 详解Tomcat内部实现架构解析
  3. 聊一聊Tomcat管理页面各类型配置
  4. 当Tomcat遇上Netty,我这一系列神操作,同事看了拍手叫绝
  5. Tomcat优化大全,进来看了,真就会了
【责任编辑:武晓燕 TEL:(010)68476606】


点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

大数据安全运维实战

大数据安全运维实战

CDH+Ambari
共20章 | cs312779641

72人订阅学习

实操案例:Jenkins持续交付和持续部署

实操案例:Jenkins持续交付和持续部署

微服务架构下的自动化部署
共18章 | freshman411

174人订阅学习

思科交换网络安全指南

思科交换网络安全指南

安全才能无忧
共5章 | 思科小牛

102人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微