Tomcat源码分析(1)—整体结构与组件.md
整体结构与组件
Tomcat是用于Java编程的开源Web服务器软件,它最初想法是托管和部署Java Servlet,通过实现Servlet接口或者继承已有类实现处理客户端发来的请求并返回响应给客户端,它本质是通过ServerSocket与客户端进行通信,在某个指定的端口上监听并接收客户端请求,通过线程分发处理请求并返回响应客户端。
从上图中可看出Tomcat中主要包含Server、Service、Connector、Engine、Host、Context、Wrapper、Servlet等组件,每个组件都有它自身的职责功能,同时可以从源码里的conf文件夹找到server.xml,配置内容的层级关系与上图是类似的。
1<?xml version="1.0" encoding="UTF-8"?>
2<Server port="8005" shutdown="SHUTDOWN">
3 <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
4 <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
5 <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
6 <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
7 <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
8
9 <GlobalNamingResources>
10 <Resource name="UserDatabase" auth="Container"
11 type="org.apache.catalina.UserDatabase"
12 description="User database that can be updated and saved"
13 factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
14 pathname="conf/tomcat-users.xml" />
15 </GlobalNamingResources>
16
17 <Service name="Catalina">
18 <Connector port="8080" protocol="HTTP/1.1"
19 connectionTimeout="20000"
20 redirectPort="8443" />
21 <Engine name="Catalina" defaultHost="localhost">
22 <Realm className="org.apache.catalina.realm.LockOutRealm">
23 <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
24 resourceName="UserDatabase"/>
25 </Realm>
26
27 <Host name="localhost" appBase="webapps"
28 unpackWARs="true" autoDeploy="true">
29 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
30 prefix="localhost_access_log" suffix=".txt"
31 pattern="%h %l %u %t "%r" %s %b" />
32
33 </Host>
34 </Engine>
35 </Service>
36</Server>
Tomcat启动时是通过读取server.xml配置的参数,加载每个对应的组件,同时该文件中配置了tomcat的吸能参数,后续可根调整此文件优化tomcat的性能,下面笔者就逐一介绍每个组件的含义及作用。
Lifecycle
该接口提供了公共的管理声明周期的方法,通过实现该接口可保持组件启动和关闭的一致性机制,Lifecycle接口主要有以下方法:
![image-20201221191406841](/Users/keres_liu/Library/Application Support/typora-user-images/image-20201221191406841.png)
- init():该方法为将要启动的组件执行初始化操作,并且在组件初始化成功后将会触发INIT_EVENT事件,如果过程中发现异常则停止初始化。
- start():为调用该组件的属性getter/setter、生命周期以外的方法做准备,并且在属性getter/setter、生命周期以外的方法调用前,需调用start()方法,在启动过程中将会触发以下事件:
- BEFORE_START_EVENT:在方法开始时触发该事件且状态变更为LifecycleState#STARTING_PREP。
- START_EVENT:在方法执行期间可安全地给组件提供调用,且状态将变为LifecycleState#STARTING,此时属性的getter/setter、生命周期以外的方法将可能被调用。
- AFTER_START_EVENT:在方法结束时、返回之前,将会触发该事件,并且状态变更为LifecycleState#STARTED。
- stop():优雅地终止对属性getter/setter、生命周期以外方法的使用,一旦STOP_EVENT触发,则getter/setter及生命周期以外的方法不应再被使用,以下事件将会顺序触发:
- BEFORE_STOP_EVENT:在stop方法被调用前会触发,状态将变更为LifecycleState#STOPPING_PREP。
- STOP_EVENT:任何子组件安全调用start()方法期间,触发该事件且状态将变更为LifecycleState#STOPPING且getter/setter、生命周期以外的方法可能不再被使用。
- AFTER_STOP_EVENT:在stop方法结束、返回之前触发该事件,状态将变更为LifecycleState#STOPPED。
- destory():在销毁对象之前被调用,并且会触发以下事件:
- DESTROY_EVENT:在对象被成功销毁之前触发该事件。
- getState():获取源组件的当前状态。
- getStateName():获取源组件当前状态的描述。
- addLifecycleListener():为组件添加声明周期事件监听器。
- findLifecycleListeners():获取生命周期关联的事件监听器。
- removeLifecycleListener():移除生命周期事件监听器。
Server
Server是tomcat容器中最顶层的组件,一个tomcat实例只有一个server组件,但一个server组件可包含多个service组件,在代码上对应tomcat的StandardServer类,该类实现了Server接口并继承了LifecycleMBeanBase,而Lifecycle是tomcat的生命周期接口,保持组件启动和关闭的一致性机制。
![image-20201221171818696](/Users/keres_liu/Library/Application Support/typora-user-images/image-20201221171818696.png)
主要核心方法:
- addService(Service service):添加新的service组件到Server。
- findService(String name):根据名称查找对应的Service组件。
- await():等待收到正确的关闭命令再返回,让主线程一直存活着且监听http连接的线程池是后台线程。
- startInternal():启动嵌套的Service组件及实现LifecycleBase#startInternal()方法。
- stopInternal():关闭嵌套Service组件及实现LifecycleBase#stopInternal()方法。
- initInternal():启动前初始化操作,例如用于绑定unix环境下的端口。
- destoryInternal():销毁嵌套的Service组件。
Service
Service是服务器内部的一个中间组件,将一个或多个连接器与一个Engine引擎紧密相连。 Service元素很少由用户定制且Engine不是必须的。
1 <Service name="Catalina">
2 <Connector port="8080" protocol="HTTP/1.1"
3 connectionTimeout="20000"
4 redirectPort="8443" />
5 <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
6 maxThreads="150" SSLEnabled="true">
7 <SSLHostConfig>
8 <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"type="RSA" />
9 </SSLHostConfig>
10 </Connector>
11 <Engine name="Catalina" defaultHost="localhost">
12 <Realm className="org.apache.catalina.realm.LockOutRealm">
13 <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
14 </Realm>
15
16 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
17 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
18 </Host>
19 </Engine>
20 </Service>
从上面的配置中,定义了一个名称为Catalina的Service,它关联了两个Connector,分别是端口为8080的Connector、端口为8443的Connector,这两个Connector与一个名称为Catalina的Engine绑定,Service组件的继承关系如下图:
![image-20201221203945180](/Users/keres_liu/Library/Application Support/typora-user-images/image-20201221203945180.png)
核心的方法主要有以下:
- setServer(Server server):设置该service组件关联的server。
- addConnector(Connector connector):添加新的连接器并关联该service组件。
- removeConnector(Connector connector):移除指定的连接器。
- addExecutor(Executor ex):添加新的执行器并关联该service。
- startInternal():启动嵌套的Executor、Connector、Container组件及重写实现LifecycleBase#startInternal()方法。
- stopInternal():停止嵌套的Executor、Connector、Container组件及重写实现LifecycleBase#stopInternal()方法。
Connector
连接器处理与客户端的通信,Tomcat提供了多个连接器,这些包括用于大多数HTTP通信的HTTP连接器,尤其是在将Tomcat作为独立服务器运行时,以及用于将Tomcat连接到Web服务器(例如Apache HTTPD服务器)时使用的AJP协议的AJP连接器,创建自定义的连接器是一项巨大的工作。
Connector 最重要的功能是接收客户端请求后分配工作线程让Container 处理请求,因此多线程的使用时Connector的核心之重。
在消息传递链路中,http应用层协议最终是通过协议层tcp进行传递消息的,而connector是监听TCP端口的组件,一个connector对应一个独立的端口,互不冲突。
在默认情况下,tomcat会提供两种协议的connector:
- 基于HTTP/1.1协议:该连接器默认对外提供8080端口,其中connectionTimeout定义连接的超时时间(毫秒),而redirectPort表示会将ssl请求重定向到8443端口。
1<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
- AJP/1.3协议的connector:该连接器主要是处理tomcat与apache服务器之间的通信交互,例如需要apache服务器做负载均衡处理静态资源,而接口则转发到tomcat服务器中,这种情况就需要基于AJP协议的连接器处理。
Connector类的继承关系图如下,它实现了Lifecycle接口,把生命周期保持与组件的同步启动、关闭。
主要的核心方法:
- Connector(String protocol):传入protocol协议并初始化连接器。
- setProtocol(String protocol):设置协议,该方法在tomcat9中将被移除,使用构造器方式初始化。
- createRequest():创建(或分配)并将适合于指定请求内容的Request对象返回给负责的Container。
- createResonse():创建(或分配)并返回一个适合于从负责的容器接收响应内容的响应对象。
- pause():停止连接器的使用。
- resume():恢复连接器的使用。
Engine
Engine引擎代表特定服务的请求处理管道,由于服务可能具有多个连接器,因此Engine引擎会接收并处理来自这些连接器的所有请求,并将响应传递回适当的连接器以传输给客户端。
引擎接口可以实现以提供自定义引擎,但这种情况并不常见,在tomcat中有一个容器的概念,它可以执行从客户端收到的请求并根据这些请求返回响应,还可以选择性通过实现Pipeline接口来支持Valve Pineline,使得以运行时配置的顺序处理请求,常用的容器主要有以下:
- Engine容器:表示整个Catalina Servlet,可能包含一个或者多个子容器,这些子容器可以是Host、Context实现或其他自定义组。
- Host:表示包含多个上下文的虚拟主机。
- Context:表示单个ServletContext,通常包含一个或者多个支持servlet的包装器。
- Wrapper:表示单个servlet定义,如果servlet本身实现SingleThreadModel,则它可以支持多个servlet实例。
Catalina的部署不需要包含上述所有的容器,例如嵌入在网络设备中(路由器)的管理应用程序只需要包含一个Context及少数Wrapper容器,甚至如果应用程序是非常小的,则只需要单个Wrapper。
一个容器可以跟许多支持的组件关联,这些组件提供共享(通过附加到父容器中)或者单独定制的功能,当前支持的组件有以下:
- Loader:加载器,用于将容器中新的Java类集成到Catalina的JVM中。
- Logger:日志,实现了ServletContext接口的log()方法声明。
- Manager:管理与该容器关联的session池。
- Realm:安全域的只读接口,用于验证用户身份及他们的身份。
- Resources:JNDI目录上下文使得可以访问静态资源,当Catalina嵌入到较大的服务时,可实现对现有服务器组件的自定义链接。
Engine容器的标准实现是org.apache.catalina.core.StandardEngine,其继承关系如下:
从图中可看到它实现了Engine接口并继承ContainerBase抽象类,在ContainerBase抽象类里完成了容器中大部分公共的功能及事件监听等。
Host
Host是一个容器,代表Catalina Servlet引擎中的虚拟主机。一个虚拟主机包含有多个Context,缺省的配置如下:
1 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
2 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />
3</Host>
- name:Host虚拟主机的名称。
- appBase:应用所在的文件路径。
- unpackWARS:配置appBase目录下的war包是否自动解压,默认为true。
- autoDeploy:是否对appBase里的应用自动发布,默认为true。
虚拟主机Host关联的父容器一般是Engine,但也可能是其他容器实现,如果不需要则可以将它省略,它在以下场景中非常有用:
- 希望使用拦截器来查看此特定虚拟主机处理的每个单个请求。
- 希望通过独立的HTTP连接器运行Catalina,但仍然想要支持多个虚拟主机。
它的标准实现是org.apache.catalina.core.StandardHost:
Context
上下文表示Web应用程序, 主机可以包含多个上下文,每个上下文具有唯一的路径,开发者可以实现Context接口来创建自定义Context,但这很少发生,因为StandardContext提供了重要的附加功能,另外附加到它的父容器通常情况是一个Host虚拟主机,但在不必须的事情下这是可以省略的,而它关联的子容器则是Wrapper(表示各个servlet)的实现。
通常情况下每一个webApp会对应一个context,每个context会提供根路径及servlet请求路径访问,其中Context接口的标准实现为:org.apache.catalina.core.StandardContext,继承关系图如下:
通常我们可以通过两种方式配置Context:
- 在server.xml中Host虚拟主机配置context上下文,并制定其访问路径等,最后可通过http://host:port/hello-word访问该上下文
1<Context path="/hello-world" docBase="/apps/hello-world" reloadable="true"/>
- 在webApp目录下创建新的文件夹,Catalina会自动加载新文件夹并创建Context,该Context的名称与文件夹是一致的。
Value
Value阀门是一个与特定容器关联的请求处理组件,通常情况下一系列的阀门互相关联成管道,也就是我们所称的责任链,而它名称的由来是因为现实世界中通过管道的阀门来控制或修改通过它的流程。
每个容器都有它的阀门处理器,默认情况下tomcat为每个容器都分配一个AccessLogValve阀门,自动记录访问日志到文件中,下面列举仅为部分阀门:
- BasicAuthenticator:基于HTTP BASIC身份验证的验证器和阀门实现。
- DigestAuthenticator:基于HTTP DIGEST身份验证的验证器和阀门实现。
- FormAuthenticator:基于FORM BASED身份验证的验证器和阀门实现。
最后总结
笔者在上面分别阐述了tomcat整体的结构以及每个容器、组件的作用,希望通过介绍每个容器或组件的作用后,读者在自己的脑海中形成对tomcat大概地了解,以及更方便后续进一步学习tomcat的每一个组件及原理,对于上述如有错误,欢迎纠正、一起交流。