美文网首页Tomcat
Tomcat之热部署和热加载源码分析

Tomcat之热部署和热加载源码分析

作者: loveFXX | 来源:发表于2020-05-27 11:43 被阅读0次

    热部署和热加载类似,都是在不重启Tomcat情况下,使最新代码生效。

    热部署与热加载的区别:

    热部署表示重新部署应用,执行容器结构是Host,表示主机。
    热加载表示重新加载类或jar包,执行容器结构是Context,表示应用。

    执行时机

    engine.start()
    org.apache.catalina.core.ContainerBase#startInternal
    开启一个定时任务执行类ContainerBackgroundProcessorMonitor
    每60s执行一次


    image.png

    org.apache.tomcat.util.threads.ScheduledThreadPoolExecutor#scheduleWithFixedDelay
    command参数是ContainerBackgroundProcessorMonitor任务类。ContainerBackgroundProcessor任务类,是第一次ContainerBackgroundProcessorMonitor任务类启动后延迟10s后每10s执行一次。


    image.png
    org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessorMonitor
    image.png
    threadStart方法是60s执行一次
    org.apache.catalina.core.ContainerBase#threadStart
    开启后台线程定期检查会话超时。backgroundProcessorDelay=10。会在utilityExecutorWrapper线程池执行ContainerBackgroundProcessor任务类
    image.png
    后台方法backgroundProcess

    org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor
    在一个固定的延迟内,执行容器及其孩子的后台方法backgroundProcess


    image.png

    1、首先执行StandardEngine的backgroundProcess方法
    org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor#processChildren
    container = StandardEngine的backgroundProcess方法


    image.png
    org.apache.catalina.core.ContainerBase#backgroundProcess
    对于这个方法是所有容器的后台任务,这些任务都需要执行:
    ①、集群服务器心跳Cluster

    如果一个容器拥有自己的类加载器,那么查看是否需要进行热加载
    检查Session是否过期


    image.png
    ②、执行每个容器对于的Realm对应的后台任务
    image.png
    ③、执行每个容器中pipeline中的每个valve的后台任务和发布PERIODIC_EVENT事件
    其中热部署就是在发布事件中
    image.png
    org.apache.catalina.util.LifecycleBase#fireLifecycleEvent
    image.png
    容器是StandardEngine时,lifecycleListeners包含MapperListener、EngineConfig这里什么都不做。
    2、StandardHost的processChildren方法
    当前容器的孩子容器是StandardHost,再次调用processChildren方法(是个递归方法)
    image.png

    StandardHost.backgroundProcess方法


    image.png
    直接看org.apache.catalina.util.LifecycleBase#fireLifecycleEvent方法
    image.png
    此时lifecycleListeners有MapperListener、ContextConfig$HostWebXmlCacheCleaner、HostConfig

    热部署

    直接查看lifecycleListeners中HostConfig的方法
    org.apache.catalina.startup.HostConfig#lifecycleEvent


    image.png

    org.apache.catalina.startup.HostConfig#check()


    image.png
    image.png
    checkResources检查资源:以进行重新部署和重新加载、对旧版本的进行检查卸载、最后进行热部署应用
    checkResources

    org.apache.catalina.startup.HostConfig#checkResources


    image.png

    检查资源文件是否被修改

    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()
            long currentTimeWithResolutionOffset =
                    System.currentTimeMillis() - FILE_MODIFICATION_RESOLUTION_MS;
            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")) {
                            // 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)
                            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;
                }
            }
            resources = app.reloadResources.keySet().toArray(new String[0]);
            boolean update = false;
            for (int i = 0; i < resources.length; i++) {
                File resource = new File(resources[i]);
                if (log.isDebugEnabled()) {
                    log.debug("Checking context[" + app.name + "] reload resource " + resource);
                }
                long lastModified = app.reloadResources.get(resources[i]).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.
                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(resources[i],
                            Long.valueOf(resource.lastModified()));
                }
                app.timestamp = System.currentTimeMillis();
            }
        }
    
    deployApps部署应用方式

    org.apache.catalina.startup.HostConfig#deployApps()


    image.png

    1、./webapps目录下部署WARs包、文件夹
    2、./conf/Catalina/localhost文件下部署

    热加载

    在调用StandardHost容器的backgroundProcess方法
    此时有7个孩子容器,这7个孩子都是在webapps目录下部署的项目,除了testservlet都是自带的。这里以自定义的testservlet测试


    image.png
    reloadable配置

    这里还不能直接测试,需要在servel.xml添加了热加载reloadable启动配置<Context path="/testservlet" docBase="testservlet" debug="0" reloadable="true"/> reloadable默认是false


    image.png

    直接走到testservlet的backgroundProcess方法


    image.png
    org.apache.catalina.core.StandardContext#backgroundProcess
    image.png

    org.apache.catalina.loader.WebappLoader#backgroundProcess
    reloadable=true才会继续调用modified()方法,查看是否修改了


    image.png
    org.apache.catalina.loader.WebappLoader#modified
    image.png
    modified

    org.apache.catalina.loader.WebappClassLoaderBase#modified
    有一个或多个classes或这resources改变将会被重新加载
    1、resourceEntries文件是否被修改


    image.png
    image.png

    resourceEntries是查看WEB-INF/classes目录下


    image.png
    2、jars是检查/WEB-INF/lib目录下是否改变
    ①、jar包添加
    image.png
    ②、jar包修改
    image.png

    ③、jar包被移除


    image.png
    所以,classes下的文件被修改,lib目录下添加、修改或移除jar包将会返回true

    总结:

    Tomcat的热部署和热加载的概念及区别,执行主体分别是什么。
    热部署是对应于应用级别
    热加载是重新加载改变的类文件或jar包的变化。

    相关文章

      网友评论

        本文标题:Tomcat之热部署和热加载源码分析

        本文链接:https://www.haomeiwen.com/subject/giqwahtx.html