Tomcat源码分析(七):热部署

热部署

热部署指的是当Tomcat启动之后,如果将新的应用添加到webapp目录下面或者是修改了应用,那么不需要重新启动tomcat就能加载新的应用

热部署的时机

从上一篇热加载我们知道,StandardEngine会启动一个ContainerBase中定义的后台线程,来执行当前容器以及所有子容器的backgroundProcess方法,并且一般容器在调用完自己的backgroundProcess方法之后,还会调用父类的backgroundProcess,也就是ContainerBase中的backgroundProcess

public void backgroundProcess() {

    if (!getState().isAvailable())
        return;

    Cluster cluster = getClusterInternal();
    if (cluster != null) {
        try {
            cluster.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString("containerBase.backgroundProcess.cluster",
                    cluster), e);
        }
    }
    Realm realm = getRealmInternal();
    if (realm != null) {
        try {
            realm.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString("containerBase.backgroundProcess.realm", realm), e);
        }
    }
    Valve current = pipeline.getFirst();
    while (current != null) {
        try {
            current.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString("containerBase.backgroundProcess.valve", current), e);
        }
        current = current.getNext();
    }
    fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}

可以看到最后会触发PERIODIC_EVENT
从前面几篇分析文章知道,host容器有一个监听器HostConfig,我们看下该类如何对这个事件进行处理

public void lifecycleEvent(LifecycleEvent event) {

    // Identify the host we are associated with
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    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();
    }
}

可以看到当接收到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 (!isServiced(app.name))
            	// 检查应用的资源判断是否需要重新部署
                checkResources(app, false);
        }

        // Check for old versions of applications that can now be undeployed
        if (host.getUndeployOldVersions()) {
            checkUndeploy();
        }

        // Hotdeploy applications
        deployApps();
    }
}

检查应用是否需要重新部署

protected synchronized void checkResources(DeployedApplication app,
            boolean skipFileModificationResolutionCheck) {
   // 获取当前应用需要重新部署的资源
   String[] resources =
       app.redeployResources.keySet().toArray(new String[0]);
   // Offset the current time by the resolution of File.lastModified()
   // 修改时间必须在当前时间的前1s才会被识别为修改,如果发生修改的时间还不足1s,不会被识别为修改
   long currentTimeWithResolutionOffset =
           System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS;
   // 下面检查应用是否需要重新部署redeploy
   for (int i = 0; i < resources.length; i++) {
       File resource = new File(resources[i]);
       if (log.isDebugEnabled())
           log.debug("Checking context[" + app.name +
                   "] redeploy resource " + resource);
       // 获取当前正在使用的资源的修改时间
       long lastModified =
               app.redeployResources.get(resources[i]).longValue();
       if (resource.exists() || lastModified == 0) {
           // File.lastModified() has a resolution of 1s (1000ms). The last
           // modified time has to be more than 1000ms ago to ensure that
           // modifications that take place in the same second are not
           // missed. See Bug 57765.
           if (resource.lastModified() != lastModified && (!host.getAutoDeploy() ||
                   resource.lastModified() < currentTimeWithResolutionOffset ||
                   skipFileModificationResolutionCheck)) {
               // 如果资源是目录,那么只是更新修改时间
               if (resource.isDirectory()) {
                   // No action required for modified directory
                   app.redeployResources.put(resources[i],
                           Long.valueOf(resource.lastModified()));
               } else if (app.hasDescriptor &&
                       resource.getName().toLowerCase(
                               Locale.ENGLISH).endsWith(".war")) {
                   // 针对使用描述符部署的war应用进行特殊处理
                   // Modified WAR triggers a reload if there is an XML
                   // file present
                   // The only resource that should be deleted is the
                   // expanded WAR (if any)
                   Context context = (Context) host.findChild(app.name);
                   String docBase = context.getDocBase();
                   if (!docBase.toLowerCase(Locale.ENGLISH).endsWith(".war")) {
                       // This is an expanded directory
                       File docBaseFile = new File(docBase);
                       if (!docBaseFile.isAbsolute()) {
                           docBaseFile = new File(host.getAppBaseFile(),
                                   docBase);
                       }
                       reload(app, docBaseFile, resource.getAbsolutePath());
                   } else {
                       reload(app, null, null);
                   }
                   // Update times
                   app.redeployResources.put(resources[i],
                           Long.valueOf(resource.lastModified()));
                   app.timestamp = System.currentTimeMillis();
                   boolean unpackWAR = unpackWARs;
                   if (unpackWAR && context instanceof StandardContext) {
                       unpackWAR = ((StandardContext) context).getUnpackWAR();
                   }
                   if (unpackWAR) {
                       addWatchedResources(app, context.getDocBase(), context);
                   } else {
                       addWatchedResources(app, null, context);
                   }
                   return;
               } else {
                   // Everything else triggers a redeploy
                   // (just need to undeploy here, deploy will follow)
                   // 一般会走这个逻辑,这里只是卸载应用
                   // 将当前应用对应的Context对象从Host的子容器中移除
                   // 并且将当前应用从deployed这个map中移除
                   undeploy(app);
                   // 删除资源
                   deleteRedeployResources(app, resources, i, false);
                   return;
               }
           }
       } else {
           // There is a chance the the resource was only missing
           // temporarily eg renamed during a text editor save
           try {
               Thread.sleep(500);
           } catch (InterruptedException e1) {
               // Ignore
           }
           // Recheck the resource to see if it was really deleted
           if (resource.exists()) {
               continue;
           }
           // Undeploy application
           undeploy(app);
           deleteRedeployResources(app, resources, i, true);
           return;
       }
   }
   // 下面检查应用是否需要reload
   resources = app.reloadResources.keySet().toArray(new String[0]);
   boolean update = false;
   for (String s : resources) {
       File resource = new File(s);
       if (log.isDebugEnabled()) {
           log.debug("Checking context[" + app.name + "] reload resource " + resource);
       }
       long lastModified = app.reloadResources.get(s).longValue();
       // File.lastModified() has a resolution of 1s (1000ms). The last
       // modified time has to be more than 1000ms ago to ensure that
       // modifications that take place in the same second are not
       // missed. See Bug 57765.
       // 当多个文件修改了,那么这里只会触发一次reload
       if ((resource.lastModified() != lastModified &&
               (!host.getAutoDeploy() ||
                       resource.lastModified() < currentTimeWithResolutionOffset ||
                       skipFileModificationResolutionCheck)) ||
               update) {
           if (!update) {
               // Reload application
               reload(app, null, null);
               update = true;
           }
           // Update times. More than one file may have been updated. We
           // don't want to trigger a series of reloads.
           app.reloadResources.put(s,
                   Long.valueOf(resource.lastModified()));
       }
       app.timestamp = System.currentTimeMillis();
   }
}

可以看到上面主要将静态文件分成了两类,每类文件的变更会触发应用的不同操作:

  1. redeployResouce,主要包含下面几个文件,以应用demo为例,如果一下文件有修改,那么会触发应用的重新部署
    (1)resource/webapps/demo.war
    (2)resource/webapps/demo
    (3)resource/conf/Catalina/localhost/demo.xml
    (4)resource/webapps/demo/META-INF/context.xml
    (5)resource/conf/context.xml
  2. reloadResourc,主要包含下面几个文件,以应用demo为例,如果一个文件由修改,那么会触发应用的重新加载
    (1)resource/webapps/demo/WEB-INF/web.xml
    (2)resource/conf/web.xml

热部署的执行

redeploy

上面可以看到当属于deployResource的文件发生修改之后,会将当前应用卸载掉,然后回到HostConfig的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 (!isServiced(app.name))
           		// 如果识别应用需要重新部署,那么会在这里将应用卸载掉,并且将应用从deployed map中移除
               checkResources(app, false);
       }

       // Check for old versions of applications that can now be undeployed
       if (host.getUndeployOldVersions()) {
           checkUndeploy();
       }

       // Hotdeploy applications
       // 这里会遍历当前所有的应用,然后通过判断应用是否在deployed这个map中,来判断是否需要进行部署,避免对一个已经部署的应用重复部署
       deployApps();
   }
}

reload

HostConfig的reload最终会调用StandardContext的reload

public synchronized void reload() {

    // Validate our current component state
    if (!getState().isAvailable())
        throw new IllegalStateException
            (sm.getString("standardContext.notStarted", getName()));

    if(log.isInfoEnabled())
        log.info(sm.getString("standardContext.reloadingStarted",
                getName()));

    // Stop accepting requests temporarily.
    setPaused(true);

    try {
        stop();
    } catch (LifecycleException e) {
        log.error(
            sm.getString("standardContext.stoppingContext", getName()), e);
    }

    try {
        start();
    } catch (LifecycleException e) {
        log.error(
            sm.getString("standardContext.startingContext", getName()), e);
    }

    setPaused(false);

    if(log.isInfoEnabled())
        log.info(sm.getString("standardContext.reloadingCompleted",
                getName()));

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值