tomcat热加载、热部署-源码解析

2022-12-01 15:40:40 浏览数 (1)

上文:tomcat线程模型-源码解析


热加载和热部署是什么?

请查看原来的写过的文章:热部署和热加载有什么区别?

tomcat热加载和执热部署都是通过后台进程检测项目中的.class和目录是否发生变化。

热加载与热部布署检测

热加载

开启热加载 在 context.xml 中配置 reloadable="true"

代码语言:javascript复制
<Context reloadable="true">

配置完后tomcat运行中会检测WEB-INF/classes和WEB-INF/lib 是否发生变化,如果发生变化进行加载。

那么热加载的流程是: 设置当前context(上下文)不能接受请求的标志为:true, 停止当前的context(上下文),启动当前context(上下文),重新设置当前context不能接收请求的标志为:false。

代码位置:org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor#run

每隔10s就会自动检测是否有代码变动。

源码实现

热部署和热加载为该线程

位置:org.apache.catalina.core.ContainerBase#threadStart

代码语言:javascript复制
protected void threadStart() {

    if (thread != null) {
        return;
    }
    if (backgroundProcessorDelay <= 0) {
        return;
    }

    threadDone = false;
    //tomcat的热加载和热部署都是通过这个线程进行的;
    String threadName = "ContainerBackgroundProcessor["   toString()   "]";
    thread = new Thread(new ContainerBackgroundProcessor(), threadName);
    thread.setDaemon(true);
    
    thread.start();

}

检测文件是否发生变动,每隔10s钟;

位置:org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor

代码语言:javascript复制
@Override
public void run() {
    Throwable t = null;
    String unexpectedDeathMessage = sm.getString(
            "containerBase.backgroundProcess.unexpectedThreadDeath",
            Thread.currentThread().getName());
    try {
        while (!threadDone) {
            try {
                //休眠10s
                Thread.sleep(backgroundProcessorDelay * 1000L);
            } catch (InterruptedException e) {
                // Ignore
            }
            if (!threadDone) {
                //检测下级变动
                processChildren(ContainerBase.this);
            }
        }
    } catch (RuntimeException|Error e) {
        t = e;
        throw e;
    } finally {
        if (!threadDone) {
            log.error(unexpectedDeathMessage, t);
        }
    }
}

位置:org.apache.catalina.core.StandardContext#backgroundProcess

代码语言:javascript复制
@Override
public void backgroundProcess() {

    if (!getState().isAvailable()) {
        return;
    }
    //webapploader每隔10s检查一次
    //检查的目录 WEB-INF/classes、web-INF/LIB 是否有文件变动
    Loader loader = getLoader();
    if (loader != null) {
        try {
            loader.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.loader", loader), e);
        }
    }
    //检查是否有有session过期
    Manager manager = getManager();
    if (manager != null) {
        try {
            manager.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.manager", manager),
                    e);
        }
    }
    //检查静态资源是否有更新
    WebResourceRoot resources = getResources();
    if (resources != null) {
        try {
            resources.backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.resources",
                    resources), e);
        }
    }
    InstanceManager instanceManager = getInstanceManager();
    if (instanceManager instanceof DefaultInstanceManager) {
        try {
            ((DefaultInstanceManager)instanceManager).backgroundProcess();
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.instanceManager",
                    resources), e);
        }
    }
    super.backgroundProcess();
}

检测变动类

源码位置:org.apache.catalina.loader.WebappLoader#backgroundProcess

代码语言:javascript复制
@Override
public void backgroundProcess() {
    //判断是否存在更新 存在进行更新
    if (reloadable && modified()) {
        try {
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (context != null) {
                context.reload();
            }
        } finally {
            if (context != null && context.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
            }
        }
    }
}

###以下是热部署检测。

热部署的事件是在检测热加载后进行的。位置是:org.apache.catalina.core.ContainerBase#backgroundProcess

org.apache.catalina.startup.HostConfig#lifecycleEvent

代码语言:javascript复制
@Override
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();
    }
}

org.apache.catalina.core.StandardContext#reload

代码语言:javascript复制
@Override
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()));
    }

}

最后

以上只是热加载和热部署的流程,其实到最后还有类的卸载和加载这里又是另一外一套打破双亲委派机制的流程,后续抽时间再深入。那么简单来说就是热加载是tomcat检测WEB-INF下面的classes和lib中文件及目录的变动而重新加载,如果重新加载后需要重新部署会暂停当前进程进行重新部署(包含新项目),当然这也是需要配置的。以上仅为简单的逻辑了解,需要深入同学请继续。还有注意一项是热加载其实是有一个监听器,通过while死循环每隔10s进行检查一次。

参考文章:

https://blog.csdn.net/chen_xiaoqi/article/details/120748629#:~:text=tomcat的热加载和热部署是通过ScheduledThreadPoolExecutor 定时线程池来实现的。 bgFuture = exec.scheduleWithFixedDelay(new ContainerBackgroundProcessor(),//要执行的Runnable,backgroundProcessorDelay, //第一次执行延迟多久 backgroundProcessorDelay, //之后每次执行间隔多久 TimeUnit.SECONDS); //时间单位 1

https://blog.51cto.com/u_11440114/3225039

https://blog.csdn.net/qq_33590654/article/details/116753362

0 人点赞