在上一篇文章中我们分析了 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 管理各种组件的行为和生命期。
参考文献:
- 书籍 – 《Tomcat 架构解析》
- Tomcat源码分析(一)------ 架构