Tomcat热加载和热部署原理详解
Tomcat热加载和热部署原理详解
在Java Web开发中,Tomcat的热加载和热部署功能可以显著提高开发效率。本文将深入探讨Tomcat热加载和热部署的原理与实现机制,帮助开发者更好地理解和运用这些功能。
Tomcat热加载和热部署
在项目开发过程中,经常要改动Java/JSP 文件,但是又不想重新启动Tomcat,有两种方式:热加载和热部署。热部署表示重新部署应用,它的执行主体是Host。 热加载表示重新加载class,它的执行主体是Context。
- 热加载:在server.xml -> context 标签中 设置 reloadable="true"
<Context docBase="D:\mvc" path="/mvc" reloadable="true" />
- 热部署:在server.xml -> Host标签中 设置 autoDeploy="true"
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
它们的区别是:
- 热加载的实现方式是 Web 容器启动一个后台线程,定期检测类文件的变化,如果有变化,就重新加载类,在这个过程中不会清空 Session ,一般用在开发环境。
- 热部署原理类似,也是由后台线程定时检测 Web 应用的变化,但它会重新加载整个 Web 应用。这种方式会清空 Session,比热加载更加干净、彻底,一般用在生产环境。
思考:Tomcat 是如何用后台线程来实现热加载和热部署的?
Tomcat开启后台线程执行周期性任务
Tomcat 通过开启后台线程ContainerBase.ContainerBackgroundProcessor,使得各个层次的容器组件都有机会完成一些周期性任务。我们在实际工作中,往往也需要执行一些周期性的任务,比如监控程序周期性拉取系统的健康状态,就可以借鉴这种设计。
Tomcat9 是通过ScheduledThreadPoolExecutor来开启后台线程的,它除了具有线程池的功能,还能够执行周期性的任务。
此后台线程会调用当前容器的 backgroundProcess 方法,以及递归调用子孙的 backgroundProcess 方法,backgroundProcess 方法会触发容器的周期性任务。
有了 ContainerBase 中的后台线程和 backgroundProcess 方法,各种子容器和通用组件不需要各自弄一个后台线程来处理周期性任务,这样的设计显得优雅和整洁。
Tomcat热加载实现原理
有了 ContainerBase 的周期性任务处理“框架”,作为具体容器子类,只需要实现自己的周期性任务就行。而 Tomcat 的热加载,就是在 Context 容器中实现的。Context 容器的 backgroundProcess 方法是这样实现的:
// StandardContext#backgroundProcess
//WebappLoader 周期性的检查 WEB-INF/classes 和 WEB-INF/lib 目录下的类文件
// 热加载
Loader loader = getLoader();
if (loader != null) {
loader.backgroundProcess();
}
WebappLoader 实现热加载的逻辑:它主要是调用了 Context 容器的 reload 方法,先stop Context容器,再start Context容器。具体的实现:
- 停止和销毁 Context 容器及其所有子容器,子容器其实就是 Wrapper,也就是说 Wrapper 里面 Servlet 实例也被销毁了。
- 停止和销毁 Context 容器关联的 Listener 和 Filter。
- 停止和销毁 Context 下的 Pipeline 和各种 Valve。
- 停止和销毁 Context 的类加载器,以及类加载器加载的类文件资源。
- 启动 Context 容器,在这个过程中会重新创建前面四步被销毁的资源。
在这个过程中,类加载器发挥着关键作用。一个 Context 容器对应一个类加载器,类加载器在销毁的过程中会把它加载的所有类也全部销毁。Context 容器在启动过程中,会创建一个新的类加载器来加载新的类文件。
Tomcat热部署实现原理
热部署跟热加载的本质区别是,热部署会重新部署 Web 应用,原来的 Context 对象会整个被销毁掉,因此这个 Context 所关联的一切资源都会被销毁,包括 Session。
Host 容器并没有在 backgroundProcess 方法中实现周期性检测的任务,而是通过监听器 HostConfig 来实现的
// HostConfig#lifecycleEvent
// 周期性任务
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
}
protected void check() {
if (host.getAutoDeploy()) {
// Check for resources modification to trigger redeployment
DeployedApplication[] apps = deployed.values().toArray(new DeployedApplication[0]);
for (DeployedApplication app : apps) {
if (tryAddServiced(app.name)) {
try {
// 检查 Web 应用目录是否有变化
checkResources(app, false);
} finally {
removeServiced(app.name);
}
}
}
// Check for old versions of applications that can now be undeployed
if (host.getUndeployOldVersions()) {
checkUndeploy();
}
// Hotdeploy applications
//热部署
deployApps();
}
}
HostConfig 会检查 webapps 目录下的所有 Web 应用:
- 如果原来 Web 应用目录被删掉了,就把相应 Context 容器整个销毁掉。
- 是否有新的 Web 应用目录放进来了,或者有新的 WAR 包放进来了,就部署相应的 Web 应用。
因此 HostConfig 做的事情都是比较“宏观”的,它不会去检查具体类文件或者资源文件是否有变化,而是检查 Web 应用目录级别的变化。