我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Tomcat源码分析 | 启动过程深度解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Tomcat源码分析 | 启动过程深度解析

Tomcat 启动逻辑层层递进,各部件协同运作。其启动流程自上而下,依次启动各个组件,如图:

图片

承接前文,我们已解析了 Catalina.load() 方法,接下来将深入探讨 daemon.start() 方法的执行过程。

Bootstrap

daemon.start()

启动过程与初始化类似,均由 Bootstrap 反射调用 Catalina 的 start 方法。

public void start()
    throws Exception {
    if( catalinaDaemnotallow==null ) init();

    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

Catalina

public void start() {

    if (getServer() == null) {
        load();
    }

    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    long t1 = System.nanoTime();

    // Start the new server
    try {
        //调用Server的start方法,启动Server组件
        getServer().start();
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    }

    // Register shutdown hook
    // 注册勾子,用于安全关闭tomcat
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    // Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
    if (await) {
        await();
        stop();
    }
}

Server

在先前的 Lifecycle 文章中,我们已阐述 StandardServer 重写了 startInternal 方法,并在此基础上实现了自身的启动逻辑。

StandardServer.startInternal

protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}

在启动过程中,LifecycleBase 首先发出 STARTING_PREP 事件,StandardServer 额外还会发出 CONFIGURE_START_EVENT 和 STARTING 事件,通知 LifecycleListener 在启动前进行准备工作。例如,NamingContextListener 会处理 CONFIGURE_START_EVENT 事件,实例化 Tomcat 相关的上下文以及 ContextResource 资源。

随后,启动 Service 组件,这部分逻辑将在后续文章中详细分析。最后,LifecycleBase 发出 STARTED 事件,完成启动流程。

Service

StandardService 的 start 代码如下所示:

  1. 启动 Engine,Engine 的子容器也会被启动,Web 应用的部署将在这一步骤完成;
  2. 启动 Executor,这是 Tomcat 使用 Lifecycle 封装的线程池,继承自 java.util.concurrent.Executor 以及 Tomcat 的 Lifecycle 接口;
  3. 启动 Connector 组件,Connector 完成 Endpoint 的启动,此时意味着 Tomcat 可以对外提供请求服务。

StandardService.startInternal

protected void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    // 启动Engine
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }

    // 启动Executor线程池
    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    // 启动MapperListener
    mapperListener.start();

    // 启动Connector
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            } catch (Exception e) {
                // logger......
            }
        }
    }
}

Engine

StandardEngine 的标准实现为 org.apache.catalina.core.StandardEngine。其构造函数的主要职责是使用默认的基础阀门创建 StandardEngine 组件。


public StandardEngine() {
    super();
    pipeline.setBasic(new StandardEngineValve());
    
    try {
        setJvmRoute(System.getProperty("jvmRoute"));
    } catch(Exception ex) {
        log.warn(sm.getString("standardEngine.jvmRouteFail"));
    }
    // By default, the engine will hold the reloading thread
    backgroundProcessorDelay = 10;
}

让我们来深入分析 StandardEngine.startInternal 方法。

StandardEngine.startInternal

@Override
protected synchronized void startInternal() throws LifecycleException {

    // Log our server identification information
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());

    // Standard container startup
    super.startInternal();
}

StandardEngine、StandardHost、StandardContext、StandardWrapper 这几个容器之间存在着父子关系。一个父容器可以包含多个子容器,并且每个子容器都对应一个父容器。Engine 是顶层父容器,它没有父容器。各个组件的包含关系如下图所示:

图片

默认情况下,StandardEngine 只有一个子容器 StandardHost,一个 StandardContext 对应一个 Web 应用,而一个 StandardWrapper 对应一个 Web 应用里面的一个 Servlet。

StandardEngine、StandardHost、StandardContext、StandardWrapper 都是继承自 ContainerBase,各个容器的启动是由父容器调用子容器的 start 方法完成的,也就是说由 StandardEngine 启动 StandardHost,再由 StandardHost 启动 StandardContext,以此类推。

由于它们都继承自 ContainerBase,当调用 start 启动 Container 容器时,首先会执行 ContainerBase 的 start 方法。ContainerBase 会寻找子容器,并在线程池中启动子容器。StandardEngine 也不例外。

ContainerBase

ContainerBase 的 startInternal 方法如下所示,主要分为以下三个步骤:

  1. 启动子容器
  2. 启动 Pipeline,并发出 STARTING 事件
  3. 如果 backgroundProcessorDelay 参数 >= 0,则开启 ContainerBackgroundProcessor 线程,用于调用子容器的 backgroundProcess 方法
protected synchronized void startInternal() throws LifecycleException {
    // 省略若干代码......

    // 把子容器的启动步骤放在线程中处理,默认情况下线程池只有一个线程处理任务队列
    Container children[] = findChildren();
    List> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }

    // 阻塞当前线程,直到子容器start完成
    boolean fail = false;
    for (Future result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            fail = true;
        }
    }

    // 启用Pipeline
    if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();
    setState(LifecycleState.STARTING);

    // 开启ContainerBackgroundProcessor线程用于调用子容器的backgroundProcess方法,默认情况下backgroundProcessorDelay=-1,不会启用该线程
    threadStart();
}

ContainerBase 会将 StartChild 任务丢给线程池处理,并获取 Future 对象。然后,它会遍历所有 Future,并进行阻塞式的 result.get() 操作,这将异步启动转换为同步启动,只有所有子容器都启动完成才会继续运行。

我们再来看看提交到线程池的 StartChild 任务,它实现了 java.util.concurrent.Callable 接口,并在 call 方法中完成子容器的 start 动作。

private static class StartChild implements Callable {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

启动 Pipeline

默认使用 StandardPipeline 实现类,它也是一个 Lifecycle。在容器启动时,StandardPipeline 会遍历 Valve 链表,如果 Valve 是 Lifecycle 的子类,则会调用其 start 方法启动 Valve 组件,代码如下:

public class StandardPipeline extends LifecycleBase
        implements Pipeline, Contained {


    protected synchronized void startInternal() throws LifecycleException {

        Valve current = first;
        if (current == null) {
            current = basic;
        }
        while (current != null) {
            if (current instanceof Lifecycle)
                ((Lifecycle) current).start();
            current = current.getNext();
        }

        setState(LifecycleState.STARTING);
    }

}

Host

在分析 Host 时,我们可以从 Host 的构造函数入手,该方法主要负责设置基础阀门。

ublic StandardHost() {
    super();
    pipeline.setBasic(new StandardHostValve());
}

StandardEngine.startInternal

protected synchronized void startInternal() throws LifecycleException {

    // errorValve默认使用org.apache.catalina.valves.ErrorReportValve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;

            // 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到  Pipeline 中
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }

            // 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
            // 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            // 处理异常,省略......
        }
    }

    // 调用父类 ContainerBase,完成统一的启动动作
    super.startInternal();
}

StandardHost Pipeline 包含的 Valve 组件:

  1. basic:org.apache.catalina.core.StandardHostValve
  2. first:org.apache.catalina.valves.AccessLogValve

需要注意的是,在往 Pipeline 中添加 Valve 阀门时,是添加到 first 后面,basic 前面。

Context

接下来,让我们深入分析 Context 的实现类 org.apache.catalina.core.StandardContext。

首先,我们来看看构造方法,该方法用于设置 Context.pipeline 的基础阀门。

public StandardContext() {
    super();
    pipeline.setBasic(new StandardContextValve());
    broadcaster = new NotificationBroadcasterSupport();
    // Set defaults
    if (!Globals.STRICT_SERVLET_COMPLIANCE) {
        // Strict servlet compliance requires all extension mapped servlets
        // to be checked against welcome files
        resourceOnlyServlets.add("jsp");
    }
}

启动方法与之前分析的容器启动方法类似,这里不再赘述。

Wrapper

Wrapper 是一个 Servlet 的包装,我们先来看看构造方法。其主要作用是设置基础阀门 StandardWrapperValve。

public StandardWrapper() {
    super();
    swValve=new StandardWrapperValve();
    pipeline.setBasic(swValve);
    broadcaster = new NotificationBroadcasterSupport();
}

这里每个容器中的 pipeline 设置的 StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve 都是非常重要的,它们在 HTTP 请求处理过程中扮演着关键角色,我们将在后续的文章中详细讲解。

结语

至此,整个启动过程便告一段落。整个启动过程由父组件控制子组件的启动,一层层往下传递,直到最后全部启动完成。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Tomcat源码分析 | 启动过程深度解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Tomcat源码分析 | 启动过程深度解析

在启动过程中,LifecycleBase 首先发出 STARTING_PREP 事件,StandardServer 额外还会发出 CONFIGURE_START_EVENT 和 STARTING 事件,通知 LifecycleListene

Tomcat源码分析 | 揭秘 Tomcat 启动-初篇

Server 只有一个 Service 组件,Service 组件先后对 Engine 和 Connector 进行初始化。而 Engine 组件并不会在初始化阶段对子容器进行初始化,Host、Context、Wrapper 容器的初始化是

[图解]Android源码分析——Activity的启动过程

Activity的启动过程一.Launcher进程请求AMSLauncher.java的startActivitySafely方法的执行过程:Activity.java中startActivity方法的执行过程:startActivityF
2022-06-06

Android启动过程深入解析

当按下Android设备电源键时究竟发生了什么?Android的启动过程是怎么样的?什么是Linux内核?桌面系统linux内核与Android系统linux内核有什么区别?什么是引导装载程序?什么是Zygote?什么是X86以及ARM l
2022-06-06

ContentProvider启动流程源码分析

本文小编为大家详细介绍“ContentProvider启动流程源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“ContentProvider启动流程源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。C
2023-07-05

如何用Play源代码分析Server启动过程

这期内容当中小编将会给大家带来有关如何用Play源代码分析Server启动过程,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Play是个Rails风格的Java Web框架。如何调试请看此处。以下进入正题
2023-06-17

深入分析GolangServer源码实现过程

这篇文章深入介绍了GolangServer源码实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
2023-02-02

【SpringBoot3.0源码】启动流程源码解析 • 上

文章目录 初始化 SpringBoot启动类: @SpringBootApplicationpublic class AppRun { public static void main(String[] args
2023-08-23

SpringBoot启动流程SpringApplication源码分析

这篇“SpringBoot启动流程SpringApplication源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“S
2023-07-05

Spring@Bean注解深入分析源码执行过程

随着SpringBoot的流行,我们现在更多采用基于注解式的配置从而替换掉了基于XML的配置,所以本篇文章我们主要探讨基于注解的@Bean以及和其他注解的使用
2023-01-10

Java线程池源码的深度解析

线程池的好处和使用本篇文章就不赘叙了,这篇文章主要通过线程池的源码带大家深入了解一下jdk8中线程池的实现,感兴趣的小伙伴可以了解一下
2022-11-13

RocketMQ源码解析broker 启动流程

这篇文章主要为大家介绍了RocketMQ源码解析broker启动流程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-23

源码深度解析之 Spring IOC

大家主要掌握 IOC 容器创建的思想和过程,以及 refresh() 的 12 个主流程即可,至于里面每一步流程,如果深究下去,其实内容非常多,建议后续如果需要,再深入学习。
Spring流程IOC2024-12-01

Springjcl及springcore源码深度解析

这篇文章主要为大家介绍了Springjcl及springcore源码深度解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

Android组件Activity的启动过程深入分析

这篇文章主要介绍了Android组件Activity的启动过程,Activity作为Android四大组件之一,他的启动没有那么简单。这里涉及到了系统服务进程,启动过程细节很多,这里我只展示主体流程。activity的启动流程随着版本的更替,代码细节一直在进行更改,每次都会有很大的修改
2023-05-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录