Apache Tomcat 初始化

在上一篇文章中我们分析了 Tomcat 中的整体架构。下面我们就来分析一下 Tomcat 源码实现。在 Tomcat 中有两个比较核心的过程:

  • Tomcat 启动:初始化 Tomcat 中的核心组件
  • 处理请求:接收客户端请求,处理并响应给客户端

这篇文章主要分析一下 Tomcat 容器的启动过程。

1、 Tomcat 核心概念

要明白 Tomcat 启动流程,首先我们先来回顾一下 Tomcat 服务器里面的核心概念:

组件名称 说明
Server 表示整个 Servlet 容器,Tomcat 运行环境中只有唯一一个 Server 实例
Service Service 表示一个或者多个 Connector 的集合,这些 Connector 共享同一个 Container 来处理其请求。在同一个 Tomcat 实例内可以包含任意多个 Service 实例,它们彼此独立
Connector Tomcat 链接器,用于监听并转化 Socket 请求,同时将读取的 Socket 请求交由 Container 处理,支持不同协议以及不同的 I/O 方式
Container Container 表示能够执行客户端请求并返回的一类对象。在 Tomcat 中存在不同级别的容器:Engine、Host、Context、Wrapper
Engine Engine 表示整个 Servlet 引擎。在 Tomcat 中,Engine 为最高层级的容器对象。尽管 Engine 不是直接处理请求的容器,却是获取目标容器的入口
Host Host 作为一类容器,表示 Servlet 引擎(即 Engine) 中的虚拟机,与一个服务器的网络名有关,如域名等。客户端可以使用这个网络名连接服务器,这个名称必须要在 DNS 服务器上注册
Context Context 作为一类容器,用于表示 ServletContext,在 Servlet 规范中,一个 ServletContext 即表示一个独立的 Web 应用
Wrapper Wrapper 作为一类容器,用于表示 Web 应用中定义的 Servlet
Executor 表示 Tomcat 组件可以共享的线程池

我们从之前的 Apache Tomcat Shell 启动文件分析 知道, Tomcat 的启动类是 Bootstrap 。

2、Tomcat 启动时序图

在 Tomcat 中是通过 Bootstrap#init 方法初始化 Bootstrap 对象实例,然后在 Bootstrap#start 方法中通过反射调用 Catalina#start 方法。下面就是 Catalina 整个时序图:
在这里插入图片描述
Tomcat 启动过程非常标准化,它的容器的核心组件都是实现了生命周期管理接口 Lifecycle 接口。首先调用 Lifecycle#init 逐级初始化,然后再调用 Lifecyle 逐级初始化。在 engine 以之前都是通过 Lifecycle 接口进行初始化, Host 及以后为了初始化效率是通过 Executor 组件进行多线程初始化。

Host 之前的组件初始化核心动作比较简单,大家都能够很容易的阅读但是 Host 及 Host 之后的初始化操作就有点复杂。下面我们就来稍微分析一下。

3、事件侦听

根据第二小节的 Tomcat 初始化时序图我们可以看出 Host 容器的初始化是通过 Engine#start 方法来初始化的。Host 对应的概念是虚拟主机,一个 Web 服务器中可以有多个虚拟主机。Tomcat 为了优化 Host 容器的初始化效率,使用多线程中的线程池来初始化 Host。

在调用 engine 初始化方法 engine#init 最终会调用到 ContainerBase#initInternal 中会初始化一个线程池:

org.apache.catalina.core.ContainerBase#initInternal

    protected void initInternal() throws LifecycleException {
        BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
        startStopExecutor = new ThreadPoolExecutor(
                getStartStopThreadsInternal(),
                getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
                startStopQueue,
                new StartStopThreadFactory(getName() + "-startStop-"));
        startStopExecutor.allowCoreThreadTimeOut(true);
        super.initInternal();
    }

接着在调用 engine#start 方法会通过上面创建的线程池通过调用线程 StartChild#call从而调用 Host 容器的 Host#start解析 server.xml 里面的 host。然后解析 webapps 下面的 web.xml,解析出最终的 Servlet。

比如 http://localhost:8080/web-demo/helloServlet

  • 虚拟主机 host – localhost:8080
  • 服务上下文 Context – web-demo
  • Servlet 包装 Wrapper – helloServlet

虚拟主机具体的解析时序图如下:

在这里插入图片描述
它的解析结合了生命周期管理 Lifecycle 接口与 MapperListener 接口,后者实现了ContainerListener 接口与 LifecycleListener 接口。

MapperListener 用于在容器组件状态发生变更时,注册或者取消对应的容器映射信息。 MapperListener 实现了 Lifecycle 接口,当 Service 启动时,会自动作为监听器注册到各个容器组件上,同时将已创建的容器注册到 Mapper 中。

在 Tomcat 中 Mapper 的作用主要是用于维护容器的映射信息,同时按照映射规则(Servlet 规范定义) 查找容器。容器启动不讨论这个组件,下一篇博客关于 Servlet 的请求处理就会讨论 Mapper。

各个组件在其生命期中会有各种各样行为,而这些行为都有触发相应的事件,Tomcat就是通过侦听这些时间达到对这些行为进行扩展的目的。在看组件的init和start过程中会看到大量如:

lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);这样的代码,这就是对某一类型事件的触发,如果你想在其中加入自己的行为,就只用注册相应类型的事件即可。

4、基于JMX

Tomcat 的核心组件都实现了 Lifecycle 实现对其生命周期的管理。在每个组件进行注册过程中,都会调用Lifecycle 的基类 LifecycleMBeanBase#initInternal通过 Registry 管理起来,而 Registry 是基于 JMX 来实现的,因此在看组件的init和start过程实际上就是初始化 MBean 和触发 MBean 的 start方法:

org.apache.catalina.util.LifecycleMBeanBase#initInternal

    protected void initInternal() throws LifecycleException {
        // If oname is not null then registration has already happened via
        // preRegister().
        if (oname == null) {
            mserver = Registry.getRegistry(null, null).getMBeanServer();

            oname = register(this, getObjectNameKeyProperties());
        }
    }

这样的代码,这实际上就是通过 JMX 管理各种组件的行为和生命期。

参考文献:

发布了195 篇原创文章 · 获赞 248 · 访问量 74万+

猜你喜欢

转载自blog.csdn.net/u012410733/article/details/105599939