Tomcat的热部署和热加载机制
说到tomcat的热部署和热加载,首先要明确什么是热部署?什么是热加载?
热加载:实现方式是 Web 容器(Context组件)启动一个后台线程,定期检测类文件的变化,如果有变化,就重新加载类,在这个
过程中不会清空 Session ,一般用在开发环境。
热部署:署原理类似,也是由后台线程定时检测 Web 应用的
变化,但它会重新加载整个 Web 应用。这种方式会清空
Session,比热加载更加干净、彻底,一般用在生产环
境
通过二者的定义,可以发现二者的实现都用到了Tomcat的后台线程。首先要明确Container容器的子组件之间的关系结构如,如下所示:热加载和热部署的实现都离不开后台线程的周期性检查,Tomcat 在基类 ContainerBase 中统一实现了后台线程的处理逻辑,并在顶层容器 Engine 启动后台线程。
- Tomcat的后台线程是怎么实现的?
Tomcat是通过ScheduledThreadPoolExecutor来启动后台线程的:
根据上面的代码可以看出,tomcat调用scheduleWithFixedDelay方法,传入四个参数。
最重要的是第一个参数:ContainerBackgroundProcessor(),它是一个 Runnable(Runnable是java实现多线程的接口,通过接口实现;与Threadl类似,只不过后者是实现多线程的类,通过继承使用),同时也是 ContainerBase 的内部类,ContainerBase 是所有容器组件的基类,我们来回忆一下容器组件有哪些,有
Engine、Host、Context 和 Wrapper 等,它们具有父子
关系
ContainerBackgroundProcessor的实现
首先ContainerBackgroundProcessor 是一个 Runnable,它需要实现 run 方法,它的 run 很简单,就是调用了processChildren 方法。这里有个小技巧,它把“宿主类”,也就是ContainerBase 的类实例当成参数传给了run 方法。而在 processChildren 方法里,就做了两步:调用当前容器的 backgroundProcess 方法,以及递归调用子孙的backgroundProcess 方法。请你注意backgroundProcess 是 Container 接口中的方法,也就是说所有类型的容器都可以实现这个方法,在这个方法里完成需要周期性执行的任务。这样的设计意味着什么呢?我们只需要在顶层容器,也就是Engine 容器中启动一个后台线程,那么这个线程不但会执行 Engine 容器的周期性任务,它还会执行所有子容器的周期性任务。
backgroundProcess 方法
上述代码都是在基类 ContainerBase 中实现的,那具体容器类需要做什么呢?其实很简单,如果有周期性任务要执行,就实现 backgroundProcess 方法;如果没有,就重用基类 ContainerBase 的方法。ContainerBase 的
backgroundProcess 方法实现如下:
有了 ContainerBase 中的后台线程和backgroundProcess 方法,各种子容器和通用组件不需要
各自弄一个后台线程来处理周期性任务,这样的设计显得优雅和整洁 - Tomcat是如何实现热加载的?
这个过程是在Context容器中实现的,Context容器的backgroundProcess方法是这样实现的:
从上面的代码我们看到 Context 容器通过 WebappLoader来检查类文件是否有更新,通过 Session 管理器来检查是否有 Session 过期,并且通过资源管理器来检查静态资源是否有更新,最后还调用了父类 ContainerBase 的backgroundProcess 方法。
这里我们要重点关注,WebappLoader 是如何实现热加载的,它主要是调用了 Context 容器的 reload 方法,而
Context 的 reload 方法比较复杂,总结起来,主要完成了下面这些任务:
- 停止和销毁 Context 容器及其所有子容器,子容器其实
就是 Wrapper,也就是说 Wrapper 里面 Servlet 实例也
被销毁了。 - 停止和销毁 Context 容器关联的 Listener 和 Filter。
- 停止和销毁 Context 下的 Pipeline 和各种 Valve。
- 停止和销毁 Context 的类加载器,以及类加载器加载的
类文件资源。 - 启动 Context 容器,在这个过程中会重新创建前面四步
被销毁的资源。
在这个过程中,类加载器发挥着关键作用。一个 Context容器对应一个类加载器,类加载器在销毁的过程中会把它加载的所有类也全部销毁。Context 容器在启动过程中,会创建一个新的类加载器来加载新的类文件。在 Context 的 reload 方法里,并没有调用 Session 管理器的 distroy 方法,也就是说这个 Context 关联的 Session是没有销毁的。你还需要注意的是,Tomcat 的热加载默认是关闭的,你需要在 conf 目录下的 Context.xml 文件中设置 reloadable 参数来开启这个功能,像下面这样:
-
Tomcat是如何实现热部署的?
- 热部署跟热加载的本质区别是,热部署会重新部署 Web 应用,原来的 Context 对象会整个被销毁掉,因此这个 Context 所关联的一切资源都会被销毁,包括 Session。那么 Tomcat 热部署又是由哪个容器来实现的呢?应该不是由 Context,因为热部署过程中 Context 容器被销毁了,那么这个重担就落在 Host 身上了,因为它是 Context 的父容器。跟 Context 不一样,Host 容器并没有在backgroundProcess 方法中实现周期性检测的任务,而是通过监听器 HostConfig 来实现的,HostConfig 就是前面提到的“周期事件”的监听器,那“周期事件”达到时,HostConfig 会做什么事呢?
它执行了 check 方法,我们接着来看 check 方法里做了什么?
其实 HostConfig 会检查 webapps 目录下的所有 Web 应用:
如果原来 Web 应用目录被删掉了,就把相应 Context 容器整个销毁掉。是否有新的 Web 应用目录放进来了,或者有新的 WAR包放进来了,就部署相应的 Web 应用。
因此 HostConfig 做的事情都是比较“宏观”的,它不会去检查具体类文件或者资源文件是否有变化,而是检查 Web应用目录级别的变化。
- 热部署跟热加载的本质区别是,热部署会重新部署 Web 应用,原来的 Context 对象会整个被销毁掉,因此这个 Context 所关联的一切资源都会被销毁,包括 Session。那么 Tomcat 热部署又是由哪个容器来实现的呢?应该不是由 Context,因为热部署过程中 Context 容器被销毁了,那么这个重担就落在 Host 身上了,因为它是 Context 的父容器。跟 Context 不一样,Host 容器并没有在backgroundProcess 方法中实现周期性检测的任务,而是通过监听器 HostConfig 来实现的,HostConfig 就是前面提到的“周期事件”的监听器,那“周期事件”达到时,HostConfig 会做什么事呢?
-
总结
今天我们学习 Tomcat 的热加载和热部署,它们的目的都是在不重启 Tomcat 的情况下实现 Web 应用的更新。
热加载的粒度比较小,主要是针对类文件的更新,通过创建新的类加载器来实现重新加载。而热部署是针对整个 Web应用的,Tomcat 会将原来的 Context 对象整个销毁掉,再重新创建 Context 容器对象。
热加载和热部署的实现都离不开后台线程的周期性检查,Tomcat 在基类 ContainerBase 中统一实现了后台线程的处理逻辑,并在顶层容器 Engine 启动后台线程,这样子容器组件甚至各种通用组件都不需要自己去创建后台线程,这样的设计显得优雅整洁。