通常情况下在开发环境中我们使用的是热加载,因为热加载的实现的方式在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运行的情况下,直接替换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在重启的过程中也会创建新的类加载器来加载我们新建的文件。
热部署
如果还是不懂热部署是什么的,下面演示一遍应该就明白了。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();
- }
- }
热部署的步骤其实也可以简化为三步骤
作者:不学无数的程序员
链接:https://juejin.im/post/5d2709d7e51d4510664d1774
来源:掘金
2020年9月22日,荣联科技集团股份有限公司战略发布暨客户答谢会在北京隆重召开,...
是否能从技术中获取价值取决于是否能一次性解决多个相互依赖的程序。 对于希望在...
近日,中科曙光面向海量异构数据自研的智能化大数据平台XData大数据智能引擎V6.0...
就目前来看,中国的游戏产业潜力仍然巨大,且有不断向国外市场扩展的趋势,如今...
【51CTO.com原创稿件】大家好,截至上期,我们有关IT项目生命周期的管理讨论,算...
美国站群服务器防攻击吗 ?大家在问这个问题的时候,出发点是因为站群服务器ip多...
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,...
很多情况下基于wcf的复杂均衡都首选zookeeper,这样可以拥有更好的控制粒度,但z...
Good morning!让我们怀着愉悦的心情打开本周的TOP云(zuntop.com)快讯吧! 国...
本文转载自微信公众号「鲜枣课堂」,作者小枣君。转载本文请联系鲜枣课堂公众号 ...