2、tomcat的结构和基础概念

Sep 13, 2016


前言

上一节我们已经通过maven把tomcat的开发环境搭建好了,现在我们可以开始阅读源码了。

在开始之前先给这次读源码定个小目标吧,比方说我先把源码背下来。

好吧开玩笑的,关于tomcat源码解读的系列博客,我准备按照从tomcat启动到处理一次完整的请求再到tomcat关闭的完整过程来读tomcat的源码,这个系列的博客也会按照这个顺序来写,尽量争取能在10月份到来之前完成这个系列的博客。

在按照这个顺序读源码的过程中,可以会遇到很多问题也很多不明白的地方,这些问题可能没办法全部一一解决,首要目标是在保证不影响我们理解的前提下选择性的忽略一些问题,影响我们对源码的理解的问题一定会彻底解决。

本节开始,我们先从宏观的角度上了解一下tomcat的设计模型,当然也会对tomcat中涉及的一些概念进行解释,因此暂时还不会真正涉及源码,可能看起来会比较无聊,不过是相当重要的,也有助于我们后边理解代码。

tomcat的设计模型

我们先来看一下tomcat的整体架构模型,如下图:

模型架构

从这个图中我们可以看出,tomcat最顶层的容器是Server,一个Server下有一个或多个Service,一个service下有一个engine,一个engine下有一个或多个host,一个host下有一个或多个context。

另外,整个容器通过connector组件与外界通信。

我们先来了解一下上面提到的这几个概念:

  • server:指的是整个应用的上下文,也是最顶层的容器,tomcat中所有的东西都在这个server里边。
  • service:指的是一个服务,主要的功能是把connector组件和engine组织起来,使得通过connector组件与整个容器通讯的应用可以使用engine提供的服务。
  • engine:服务引擎,这个可以理解为一个真正的服务器,内部提供了多个虚拟主机对外服务。
  • host:虚拟主机,每一个虚拟主机相当于一台服务器,并且内部可以部署多个应用,每个虚拟主机可以绑定一个域名,并指定多个别名。
  • context:应用上下文,每一个webapp都有一个单独的context,也可以理解为每一个context代表一个webapp。
  • connector:连接器组件,可以配置多个连接器支持多种协议,如http,APJ等

注意: 在网上有看到其他人的博客,其中提到service下可以有一个或多个engine,这里必须明确说明一下,service和engine实际上是一对一的关系,一个service中只有一个engine,一个engine也只能属于一个service,这一点在官方文档中有明确说明的,另外,代码中的实现也不支持多个engine。

接下来我们来看一下各个组件的实现类和它们之间的关系。

Server

我们先从最顶层的Server开始,在tomcat真正使用的实现类是org.apache.catalina.core.StandardServer,下面是这个类的层次结构图:

org.apache.catalina.core.StandardServer

从这个结构图中我们可以看出,Tomcat中定义了Server这个接口来抽象一个server,并且最终由StandardServer作为实现类,同时接口Server继承了另一个更顶层的接口Lifecycle,这表明Server本身也有生命周期。

Service

server下面包含一个或多个service,在Tomcat中,定义了一个接口Service来抽象这个架构中的service,并且最终使用org.apache.catalina.core.StandardService作为实现类,整个类的层次结构图如下:

org.apache.catalina.core.StandardService

这里我可以看到,Service接口也继承了Lifecycle接口,表明它是有一定生命周期的。

Engine

同样的在Tomcat中也定义了Engine接口,用org.apache.catalina.core.StandardEngine作为最终的实现类,类层次结构如下:

org.apache.catalina.core.StandardEngine

同样的Engine也是有一定生命周期的,所以Engine继承了Lifecycle接口。

Host

host代表一个虚拟主机,每个虚拟主机可以绑定一个域名和多个别名,Tomcat中使用Host接口代表一个host,最终的实现类是org.apache.catalina.core.StandardHost,类的结构层次如下:

org.apache.catalina.core.StandardHost

同样的我们可以看到,Host也继承了Lifecycle接口。

Context

最后一个容器是context,在Tomcat中用Context接口表示,它的默认实现是org.apache.catalina.core.StandardContext,类的结构层次如下:

org.apache.catalina.core.StandardContext

Context也继承了Lifecycle

Connector

connector在Tomcat中并没有定义接口,而是直接使用实现类org.apache.catalina.connector.Connector来表示连接器,类的层次结构图如下:

org.apache.catalina.connector.Connector

这里我们依然可以看到,org.apache.catalina.connector.Connector也实现了Lifecycle接口。

tomcat组件的生命周期

从前面的类层次图我们可以知道,org.apache.catalina.Lifecycle是一个非常重要的接口,所有的组件和容器最终都实现了这个接口。实际上这个接口定义的是Tomcat中组件的生命周期,并且定义了每个生命周期的入口方法,同时,还定义了不同生命周期组件的不同状态,我们看一下这个接口定义的所有属性和方法:

org.apache.catalina.Lifecycle

从这个类我们可以看到,这里主要通过抽象方法定义了四个生命周期:

  • init() : 初始化
  • start() : 启动阶段
  • stop() : 停止阶段
  • destory() : 销毁阶段

因此所有的Tomcat组件生命周期都有这个四个阶段。

另外,我们可以看到这里有很多属性,这些属性表示的是组件在生命周期变化时触发的事件,组件完整的生命周期状态在org.apache.catalina.LifecycleState这个枚举类中定义,一共定义了14个状态:

org.apache.catalina.LifecycleState

实际上MUST_STOPMUST_DESTORY这两个状态已经被废弃了,所以在整个生命周期中,只有12个状态。

这12个状态和生命周期有密切的关系,我们来看一下在四个生命周期中,这12个状态是如何变化的。

tomcat的状态转换

从上图可以看出,四个生命周期变化的过程,伴随着12个状态的变化。

另外,我们需要特别注意,org.apache.catalina.util.LifecycleBase这个抽象类,它实现了Lifecycle的大部分方法,并且包装了一些抽象方法出来,这个类的结构图如下:

tomcat_lifecycle_base

这里可以看到,LifecycleBase把四个生命周期的方法重新封装成initInternal(),startInternal(),stopInternal(),destroyInternal()四个抽象方法了,实际上,这个封装是为了统一实现生命周期中的状态控制,而不需要实现类自己去关心这个过程的状态变化。这是一个重要的抽象类,后边的章节经常会提到。

Container

除了声明生命周期的Lifecycle接口,我们还需要特别注意Container这个接口,ContextHostEngine都继承了这个接口,这个接口有一个重要的方法,public Pipeline getPipeline(),这个方法是用来获取当前容器的管道对象的,实际上,tomcat在处理请求的时候,是通过一个管道来处理的,这个管道上可以配置多个阀门,处理请求的时候,请求的数据会经过所有阀门,zge正常处理请求会有四个阀门:

org.apache.catalina.core.StandardEngineValve
org.apache.catalina.core.StandardHostValve
org.apache.catalina.core.StandardContextValve
org.apache.catalina.core.StandardWrapperValve

当然,出现错误也会使用错误处理阀门:

org.apache.catalina.valves.ErrorReportValve

另外阀门也可以由我们自己在server.xml中配置:

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="${catalina.base}/logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

这里需要我们理解管道和阀门的概念,在后边讲到请求处理时会真正用到。

一次请求的过程

我把tomcat中对一次请求的处理划分为如下几个阶段:

  • 接收阶段:接收请求,将请求放入待处理队列
  • 协议解析阶段:根据请求协议解析请求,构造协议标准对象,并交给对应的Servlet
  • 请求处理阶段:Servlet处理请求
  • 结束阶段:servlet结束处理,tomcat结束本次请求

总结

读完本章,虽然我们目前还不是很了解tomcat的源码实现,不过大致上对tomcat的架构有了了解,这对我们后边理解tomcat的源码至关重要,后续的章节,我们将按照如下顺序来读源码:

  • tomcat的启动
  • tomcat请求处理
  • tomcat的停止

由于请求交给servlet处理的过程是应用自己写的servlet,跟tomcat的源码无关,所以这个系列的博客不会提及。