前言
上一节我们已经通过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
,下面是这个类的层次结构图:
从这个结构图中我们可以看出,Tomcat中定义了Server
这个接口来抽象一个server,并且最终由StandardServer
作为实现类,同时接口Server
继承了另一个更顶层的接口Lifecycle
,这表明Server
本身也有生命周期。
Service
server下面包含一个或多个service,在Tomcat中,定义了一个接口Service
来抽象这个架构中的service,并且最终使用org.apache.catalina.core.StandardService
作为实现类,整个类的层次结构图如下:
这里我可以看到,Service
接口也继承了Lifecycle
接口,表明它是有一定生命周期的。
Engine
同样的在Tomcat中也定义了Engine
接口,用org.apache.catalina.core.StandardEngine
作为最终的实现类,类层次结构如下:
同样的Engine
也是有一定生命周期的,所以Engine
继承了Lifecycle
接口。
Host
host代表一个虚拟主机,每个虚拟主机可以绑定一个域名和多个别名,Tomcat中使用Host
接口代表一个host,最终的实现类是org.apache.catalina.core.StandardHost
,类的结构层次如下:
同样的我们可以看到,Host
也继承了Lifecycle
接口。
Context
最后一个容器是context,在Tomcat中用Context
接口表示,它的默认实现是org.apache.catalina.core.StandardContext
,类的结构层次如下:
Context
也继承了Lifecycle
。
Connector
connector在Tomcat中并没有定义接口,而是直接使用实现类org.apache.catalina.connector.Connector
来表示连接器,类的层次结构图如下:
这里我们依然可以看到,org.apache.catalina.connector.Connector
也实现了Lifecycle
接口。
tomcat组件的生命周期
从前面的类层次图我们可以知道,org.apache.catalina.Lifecycle
是一个非常重要的接口,所有的组件和容器最终都实现了这个接口。实际上这个接口定义的是Tomcat中组件的生命周期,并且定义了每个生命周期的入口方法,同时,还定义了不同生命周期组件的不同状态,我们看一下这个接口定义的所有属性和方法:
从这个类我们可以看到,这里主要通过抽象方法定义了四个生命周期:
- init() : 初始化
- start() : 启动阶段
- stop() : 停止阶段
- destory() : 销毁阶段
因此所有的Tomcat组件生命周期都有这个四个阶段。
另外,我们可以看到这里有很多属性,这些属性表示的是组件在生命周期变化时触发的事件,组件完整的生命周期状态在org.apache.catalina.LifecycleState
这个枚举类中定义,一共定义了14个状态:
实际上MUST_STOP
和MUST_DESTORY
这两个状态已经被废弃了,所以在整个生命周期中,只有12个状态。
这12个状态和生命周期有密切的关系,我们来看一下在四个生命周期中,这12个状态是如何变化的。
从上图可以看出,四个生命周期变化的过程,伴随着12个状态的变化。
另外,我们需要特别注意,org.apache.catalina.util.LifecycleBase
这个抽象类,它实现了Lifecycle
的大部分方法,并且包装了一些抽象方法出来,这个类的结构图如下:
这里可以看到,LifecycleBase
把四个生命周期的方法重新封装成initInternal()
,startInternal()
,stopInternal()
,destroyInternal()
四个抽象方法了,实际上,这个封装是为了统一实现生命周期中的状态控制,而不需要实现类自己去关心这个过程的状态变化。这是一个重要的抽象类,后边的章节经常会提到。
Container
除了声明生命周期的Lifecycle
接口,我们还需要特别注意Container
这个接口,Context
、Host
、Engine
都继承了这个接口,这个接口有一个重要的方法,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 "%r" %s %b" />
这里需要我们理解管道和阀门的概念,在后边讲到请求处理时会真正用到。
一次请求的过程
我把tomcat中对一次请求的处理划分为如下几个阶段:
- 接收阶段:接收请求,将请求放入待处理队列
- 协议解析阶段:根据请求协议解析请求,构造协议标准对象,并交给对应的Servlet
- 请求处理阶段:Servlet处理请求
- 结束阶段:servlet结束处理,tomcat结束本次请求
总结
读完本章,虽然我们目前还不是很了解tomcat的源码实现,不过大致上对tomcat的架构有了了解,这对我们后边理解tomcat的源码至关重要,后续的章节,我们将按照如下顺序来读源码:
- tomcat的启动
- tomcat请求处理
- tomcat的停止
由于请求交给servlet处理的过程是应用自己写的servlet,跟tomcat的源码无关,所以这个系列的博客不会提及。