Tomcat

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

2020-05-27  本文已影响0人  love111

热部署和热加载类似,都是在不重启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包的变化。

上一篇 下一篇

猜你喜欢

热点阅读