Tomcat卷一
- Tomcat 基础概念扫盲
- web 概念
- 常见的web服务器
- 概念
- 常见web服务器软件
- Tomcat 历史
- Tomcat 安装
- Tomcat 目录结构
- Tomcat 启动停止
- Tomcat源码
-
- 运行
- Tomcat 架构
-
- Http工作原理
- Tomcat整体架构
-
- Http服务器请求处理
- Servlet容器工作流程
- Tomcat整体架构
- tomcat核心组件分析
-
- tomcat核心组件协作过程
- 演示
- tomcat体系结构大白话
- pipeline组件
- 连接器 - Coyote
-
- 架构介绍
- IO模型与协议
- 连接器组件
-
- EndPoint
- Processor
- ProtocolHandler
- Adapter
- 容器 - Catalina
-
- Catalina 地位
- Catalina 结构
- Container 结构
- Tomcat 启动流程
-
- 流程
- 源码解析
-
- Lifecycle
- 各组件的默认实现
- init源码流程追踪
-
- 1.Tomcat启动入口
-
- 1.1main函数
- 1.1.1bootstrap实例的init方法
- 1.1.2 反射调用Catalina实例的load方法
- 2.Catalina实例的load方法
-
- 2.1解析server.xml或server-embed.xml文件
- 2.2 初始化Server实例
-
- 2.2.1 getServer().init() ---> StandardServer.initInternal()
- 1.Service初始化 --->StandardService.initInternal()
-
- 1.1 Engine初始化 ---> StandardEngine.initInternal()
-
- 1.1.1 StartStopExecutor初始化 ---> StandardEngine父类ContainerBase.initInternal()
- 1.2 Connector初始化 ---> Connector.initInternal()
-
- 1.2.1 CoyoteAdpater适配器初始化
- 1.2.2 ProtocolHandler初始化 ---> AbstractProtocol.init()
- 1.2.3 Enpoint.bind() ---> Enpoint.init()调用NioEndpoint.bind()
- start源码追踪
-
- BootStrap.start() ---> Catalina.start()
- Server.start() ---> StandardServer.startInternal()
- Service.start() ---> StandardService.startInternal()
- Engine.start() --->StandardEngine.startInternal()
- Host.start() ---> StandardHost.startInternal()
-
- 5.1 HostConfig.start()
- StandardContext.startInternal()
-
- 6.1 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null) ---> Context.configureStart()
- 6.2 webConfig() ---> processClasses(webXml, orderedFragments)
- StandardWrapper.startInternal()
- Connector.startInternal()
- 9.ProtocolHandler.start() ---> AbstractProtocol.start()
- 10.Endpoint.start() ---> NioEndpoint.startInternal()
-
- Poller.run()
-
- Poller.processKey()
- AbstractEndpoint.processSocket()
- NioEndpoint.SocketProcessor.doRun()
- AbstractProtocol.ConnectionHandler.process
- startAcceptorThreads
- createAcceptor()--->acceptor---->run
- 总结
本系列主要在于梳理tomcat的整体架构和源码剖析,只挑重点流程进行分析
Tomcat 基础概念扫盲
web 概念
代码语言:javascript复制1). 软件架构
1. C/S: 客户端/服务器端 ‐‐‐‐‐‐‐‐‐‐‐‐> QQ , 360 ....
2. B/S: 浏览器/服务器端 ‐‐‐‐‐‐‐‐‐‐‐‐> 京东, 网易 , 淘宝
2). 资源分类
1. 静态资源: 所有用户访问后,得到的结果都是一样的,称为静态资源。静态资
源可以直接被浏览器解析。
* 如: html,css,JavaScript,jpg
2. 动态资源: 每个用户访问相同资源后,得到的结果可能不一样 , 称为动态资
源。动态资源被访问后,需要先转换为静态资源,再返回给浏览器,通过浏览器进行解析。
* 如:servlet/jsp,php,asp....
3). 网络通信三要素
1. IP:电子设备(计算机)在网络中的唯一标识。
2. 端口:应用程序在计算机中的唯一标识。 0~65536
3. 传输协议:规定了数据传输的规则
1. 基础协议:
1. tcp : 安全协议,三次握手。 速度稍慢
2. udp:不安全协议。 速度快
常见的web服务器
概念
代码语言:javascript复制1). 服务器:安装了服务器软件的计算机
2). 服务器软件:接收用户的请求,处理请求,做出响应
3). web服务器软件:接收用户的请求,处理请求,做出响应。
在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目
常见web服务器软件
代码语言:javascript复制1). webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
2). webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
3). JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
4). Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范 servlet/jsp。开源的,免费的。
Tomcat 历史
1) Tomcat 最初由Sun公司的软件架构师 James Duncan Davidson 开发,名称为 “JavaWebServer”。
2) 1999年 ,在 Davidson 的帮助下,该项目于1999年于apache 软件基金会旗下的 JServ 项目合并,并发布第一个版本(3.x), 即是现在的Tomcat,该版本实现了 Servlet2.2 和 JSP 1.1 规范 。
3) 2001年,Tomcat 发布了4.0版本, 作为里程碑式的版本,Tomcat 完全重新设计了 其架构,并实现了 Servlet 2.3 和 JSP1.2规范。
Tomcat 安装
Tomcat官网下载源码包即可,这里使用的是tomcat 8.5.42版本
Tomcat 目录结构
Tomcat 启动停止
启动
代码语言:javascript复制双击 bin/startup.bat 文件
停止
代码语言:javascript复制双击 bin/shutdown.bat 文件
访问
代码语言:javascript复制http://localhost:8080
Tomcat源码
Tomcat 8.5源码包下载
运行
1) 解压zip压缩包
2) 进入解压目录,并创建一个目录,命名为home , 并将conf、webapps目录移入 home 目录中
3) 在当前目录下创建一个 pom.xml 文件,引入tomcat的依赖包
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>Tomcat8.5</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<testSourceDirectory>test</testSourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>test</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxrpc</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.5.1</version>
</dependency>
</dependencies>
</project>
4) 在idea中, 导入该工程
5) 配置idea的启动类, 配置 MainClass , 并配置 VM 参数
由于移动了源码文件的位置,因此需要手动指定新文件的位置,这里用绝对路径,改成自己的绝对路径防止文件找不到
代码语言:javascript复制-Dcatalina.home=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home
-Dcatalina.base=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home
-Djava.endorsed.dirs=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/endorsed
-Djava.io.tmpdir=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/conf/logging.properties
6) 启动主方法, 运行Tomcat , 访问Tomcat 。
说明:如果编译build的时候出现Test测试代码报错,注释该代码即可。本文中的Tomcat源码util.TestCookieFilter类会报错,将其注释即可
运行项目,访问http://localhost:8080,得到结果:
原因是我们直接启动org.apache.catalina.startup.Bootstrap
的时候没有加载org.apache.jasper.servlet.JasperInitializer
,从而无法编译JSP
。
解决办法是在tomcat
的源码org.apache.catalina.startup.ContextConfig
中的configureStart
函数中手动将JSP
解析器初始化:
protected synchronized void configureStart() {
// Called from StandardContext.start()
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.start"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.xmlSettings",
context.getName(),
Boolean.valueOf(context.getXmlValidation()),
Boolean.valueOf(context.getXmlNamespaceAware())));
}
webConfig();
//这里是需要添加的代码,其余是源码不需要动
context.addServletContainerInitializer(new JasperInitializer(), null);
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
validateSecurityRoles();
}
// Configure an authenticator if we need one
if (ok) {
authenticatorConfig();
}
// Dump the contents of this pipeline if requested
if (log.isDebugEnabled()) {
log.debug("Pipeline Configuration:");
Pipeline pipeline = context.getPipeline();
Valve valves[] = null;
if (pipeline != null) {
valves = pipeline.getValves();
}
if (valves != null) {
for (int i = 0; i < valves.length; i ) {
log.debug(" " valves[i].getClass().getName());
}
}
log.debug("======================");
}
// Make our application available if no problems were encountered
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
修改完后,项目再启动,我们再在浏览器访问http://localhost:8080/ ,就可以看到我们所熟悉的经典欢迎页面了
Tomcat 架构
Http工作原理
HTTP协议是浏览器与服务器之间的数据传送协议。
作为应用层协议,HTTP是基于TCP/IP 协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包 (Packet)传输,主要规定了客户端和服务器之间的通信格式。
从图上你可以看到,这个过程是:
1) 用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览 器获取了这个事件。
2) 浏览器向服务端发出TCP连接请求。
3) 服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接。
4) 浏览器将请求数据打包成一个HTTP协议格式的数据包。
5) 浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。
6) 服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。
7) 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。
8) 服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。
9) 服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。
10) 浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是 HTML。
11) 浏览器将HTML文件展示在页面上。
那我们想要探究的Tomcat作为一个HTTP服务器,在这个过程中都做了些什么事情呢?
主 要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。
Tomcat整体架构
Http服务器请求处理
浏览器发给服务端的是一个HTTP格式的请求,HTTP服务器收到这个请求后,需要调用服 务端程序来处理,所谓的服务端程序就是你写的Java类,一般来说不同的请求需要由不同 的Java类来处理。
1) 图1 , 表示HTTP服务器直接调用具体业务类,它们是紧耦合的。
2) 图2,HTTP服务器不直接调用业务类,而是把请求交给容器来处理,容器通过 Servlet接口调用业务类。
因此Servlet接口和Servlet容器的出现,达到了HTTP服务器与 业务类解耦的目的。
而Servlet接口和Servlet容器这一整套规范叫作Servlet规范。
Tomcat按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功 能。
作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet,并把它 注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了。
Servlet容器工作流程
为了解耦,HTTP服务器不直接调用Servlet,而是把请求交给Servlet容器来处理,那 Servlet容器又是怎么工作的呢?
当客户请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户的请求信息封 装起来,然后调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL 和Servlet的映射关系,找到相应的Servlet,如果Servlet还没有被加载,就用反射机制创 建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法 来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给 客户端。
Tomcat整体架构
我们知道如果要设计一个系统,首先是要了解需求,我们已经了解了Tomcat要实现两个 核心功能:
1) 处理Socket连接,负责网络字节流与Request和Response对象的转化。
2) 加载和管理Servlet,以及具体处理Request请求。
因此Tomcat设计了两个核心组件连接器(Connector)
和容器(Container)
来分别做这 两件事情。连接器负责对外交流,容器负责内部处理。
tomcat核心组件分析
- Connector处理 http协议 端口号
- Host对应域名
- Context是tomcat应用执行上下文,默认是/,这里设置为lunban
- Servlet对应资源: 这里分为动态资源和静态资源,静态资源例如后缀是.html,.jpg的直接返回即可,动态资源还需要交给Servlet进行处理后才能返回处理后的静态资源
tomcat核心组件协作过程
科普时间到:
- 端口号的作用
端口号可以用来标识同一个主机上通信的不同应用程序,端口号 IP地址就可以组成一个套接字,用来标识一个进程
- 一个进程是否可以bind多个端口号?
可以, 因为一个进程可以打开多个文件描述符,而每个文件描述符都对应一个端口号,所以一个进程可以绑定多个端口号
- 一个端口号是否可以被多个进程绑定?
不可以, 如果进程先绑定一个端口号,然后在fork一个子进程,这样的话就可以是实现多个进程绑定一个端口号,但是两个不同的进程绑定同一个端口号是不可以的
这里tomcat服务器也可以设置多个端口号,因此对于的连接器也可以有多个
因为域名也可以配置多个,因此站点对应的也可以有多个
这里得出结论1: 连接器和站点是多对多的关系
结论2: 站点和应用上下文是一对多的关系,一个站点下面可以有多个应用上下文,这里应用上下文映射到物理层面对应一个文件夹,然后文件夹下面放着资源
结论3:应用上下文和资源也是一对多的关系,上面也说了,可以把应用上下文本质就是一个文件夹,而所谓的资源就是文件夹下面的.java文件动态资源文件或者.html,.jps等静态资源文件
应用上下文本质就是一堆资源文件集合存放的文件夹
演示
这是一个删减过后的server.xml:
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<!-- 连接器可以存在多个-->
<Connector port="8080" protocol="HTTP/1.1"/>
<Connector port="8081" protocol="HTTP/1.1"/>
<!-- engine可以看做是管理Host的一个容器,这里 defaultHost规定了默认的访问站点 -->
<Engine name="Catalina" defaultHost="localhost">
<!-- 站点也可以存在多个,这里对应Host标签里面的name -->
<!--这里默认的应用上下文是/,这里appBase是当前站点的根路径 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
<Host name="www.dhy.com" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
我们再来看一下应用上下文:
如果我们设置应用上下文:
上面context目录下面的index.jsp就是静态资源,可以直接返回,对于一些动态资源,我们需要通过java代码进行处理后,才能处理为静态资源返回给浏览器
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<!-- 连接器可以存在多个-->
<Connector port="80" protocol="HTTP/1.1"/>
<Connector port="8081" protocol="HTTP/1.1"/>
<!-- engine可以看做是管理Host的一个容器,这里 defaultHost规定了默认的访问站点 -->
<Engine name="Catalina" defaultHost="localhost">
<!-- 站点也可以存在多个,这里对应Host标签里面的name -->
<!--这里默认的应用上下文是/,这里appBase是当前站点的根路径 -->
<!-- unpackWAR决定是否自动解压当前appBase路径下面的war包 -->
<!-- autoDeploy决定是否自动部署,也就是解压完对应war后,会启动相应的应用服务 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
<Host name="www.dhy.com" appBase="dhy"
unpackWARs="true" autoDeploy="true">
<!-- 这里可以填相对路径或者绝对路径,相对路径对应Host的appBase路径-->
<context path="/xpyWithDhy" docBase="C:UserszdhDesktoptomcattomcatSrcCodeapache-tomcat-8.5.42-srchomeXPY" ></context>
</Host>
</Engine>
</Service>
</Server>
tomcat体系结构大白话
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<!--医院-->
<Server>
<!-- 医院一号楼-->
<Service name="one">
<!--一号楼的A入口-->
<Connector port="80" protocol="HTTP/1.1"/>
<!--一号楼的B入口-->
<Connector port="8081" protocol="HTTP/1.1"/>
<!--进入大楼后,要挂号,如果不知道挂哪一个科室,有一个默认科室,是全能的-->
<Engine name="call" defaultHost="localhost">
<!--挂号要挂哪一个科室-->
<!-- 默认科室 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
<!-- 骨科室,name是骨科室的门牌号,appBase是骨科室的具体地址-->
<Host name="www.dhy.com" appBase="dhy"
unpackWARs="true" autoDeploy="true">
<!--骨科室内的医生,path医生的工位号,docBase指明医生的具体位值-->
<context path="/xpy" docBase="xpy" ></context>
</Host>
</Engine>
</Service>
<!-- 医院二号楼-->
<Service name="one">
...
</Service>
</Server>
最后医生会给病人开药,这里的药就看做是context目录下拥有的资源
pipeline组件
先看tomcat的请求流转流程:
思考一个问题:如果我们想在上述某个流程加入日志打印功能,或者由于同一时间请求数过多需要进行限流操作,怎么设计最合适呢?
我们看一下tomcat是怎样处理的吧
每一个组件都对应一个pipeline管道,然后管道中流动的是浏览器请求来的数据,而管道里面有很多的阀门,我们可以在任意一个地方将阀门关死,代表拒绝此次请求处理,或者根据条件进行判断,或者进行一些记录功能
如果我们不向管道中加入阀门,那么每个组件对应的管道中都有自己的一个默认实现
如果我们需要手动加入阀门,就需要修改server.xml来完成:
源码先不将,重点在于先理解tomcat体系结构,方便下面讲源码的时候,可以从全局架构里面代码的设计思想
这里使用的是责任链模式,pipeline直接的串联方式是链表实现
连接器 - Coyote
架构介绍
Coyote 是Tomcat的连接器框架的名称 , 是Tomcat服务器提供的供客户端访问的外部接口。
客户端通过Coyote与服务器建立连接、发送请求并接受响应 。
Coyote 封装了底层的网络通信(Socket 请求及响应处理),为Catalina 容器提供了统一 的接口,使Catalina 容器与具体的请求协议及IO操作方式完全解耦。
Coyote 将Socket 输 入转换封装为 Request 对象,交由Catalina 容器进行处理,处理请求完成后, Catalina 通 过Coyote 提供的Response 对象将结果写入输出流 。
Coyote作为独立的模块,只负责具体协议和IO的相关操作,与Servlet 规范实现没有直接关系,因此即便是 Request 和 Response对象也并未实现Servlet规范对应的接口, 而是在Catalina 中将他们进一步封装为ServletRequest和ServletResponse。
IO模型与协议
在Coyote中 , Tomcat支持的多种I/O模型和应用层协议,具体包含哪些IO模型和应用层 协议,请看下表:
Tomcat 支持的IO模型(自8.5/9.0 版本起,Tomcat 移除了 对 BIO 的支持):
Tomcat 支持的应用层协议 :
协议分层 :
在 8.0 之前 , Tomcat 默认采用的I/O方式为 BIO , 之后改为 NIO。
无论 NIO、NIO2 还是 APR, 在性能方面均优于以往的BIO。
如果采用APR, 甚至可以达到 Apache HTTP Server 的性能。
Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器
,就好比 一个房间有多个门。
但是单独的连接器或者容器都不能对外提供服务,需要把它们组装 起来才能工作,组装后这个整体叫作Service组件。
这里请你注意,Service本身没有做什 么重要的事情,只是在连接器和容器外面多包了一层
,把它们组装在一起。
Tomcat内可 能有多个Service,这样的设计也是出于灵活性的考虑。
通过在Tomcat中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。
连接器组件
连接器中的各个组件的作用如下:
EndPoint
1) EndPoint : Coyote 通信端点,即通信监听的接口,是具体Socket接收和发送处理 器,是对传输层的抽象,因此EndPoint用来实现TCP/IP协议的。
2) Tomcat 并没有EndPoint 接口,而是提供了一个抽象类AbstractEndpoint
, 里面定 义了两个内部类:Acceptor和SocketProcessor。
Acceptor用于监听Socket连接请求。
SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里 调用协议处理组件Processor进行处理。
为了提高处理能力,SocketProcessor被提交到 线程池来执行。
而这个线程池叫作执行器(Executor),后面会详细介绍 Tomcat如何扩展原生的Java线程池。
Processor : Coyote 协议处理接口 ,如果说EndPoint是用来实现TCP/IP协议的,那么 Processor用来实现HTTP协议,
Processor接收来自EndPoint的Socket,读取字节流解 析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理, Processor是对应用层协议的抽象。
ProtocolHandler
ProtocolHandler: Coyote 协议接口, 通过Endpoint 和 Processor
, 实现针对具体协 议的处理能力。
Tomcat 按照协议和I/O 提供了6个实现类 : AjpNioProtocol , AjpAprProtocol, AjpNio2Protocol , Http11NioProtocol ,Http11Nio2Protocol , Http11AprProtocol。
我们在配置tomcat/conf/server.xml 时 , 至少要指定具体的 ProtocolHandler , 当然也可以指定协议名称 , 如 : HTTP/1.1 ,如果安装了APR,那么 将使用Http11AprProtocol , 否则使用 Http11NioProtocol 。
Adapter
由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类 来“存放”这些请求信息。
ProtocolHandler
接口负责解析请求并生成Tomcat Request
类。
但是这个Request对象不是标准的ServletRequest,也就意味着,不能用Tomcat Request作为参数来调用容器。
Tomcat设计者的解决方案是引入CoyoteAdapter
,这是 适配器模式的经典运用,连接器调用CoyoteAdapter
的Sevice
方法,传入的是Tomcat Request
对象,CoyoteAdapter
负责将Tomcat Request
转成ServletRequest
,再调用容 器的Service
方法。
容器 - Catalina
Tomcat是一个由一系列可配置的组件构成的Web容器,而Catalina是Tomcat的servlet容 器。
Catalina 是Servlet 容器实现,包含了之前讲到的所有的容器组件,以及后续章节涉及到 的安全、会话、集群、管理等Servlet 容器架构的各个方面。
它通过松耦合的方式集成 Coyote,以完成按照请求协议进行数据读写。同时,它还包括我们的启动入口、Shell程 序等。
Catalina 地位
Tomcat 的模块分层结构图, 如下:
Tomcat 本质上就是一款 Servlet 容器, 因此Catalina 才是 Tomcat 的核心 , 其他模块 都是为Catalina 提供支撑的。 比如 : 通过Coyote 模块提供链接通信,Jasper 模块提供 JSP引擎,Naming 提供JNDI 服务,Juli 提供日志服务。
Catalina 结构
Catalina 的主要组件结构如下:
如上图所示,Catalina负责管理Server,而Server表示着整个服务器。Server下面有多个 服务Service,每个服务都包含着多个连接器组件Connector(Coyote 实现)和一个容器 组件Container。在Tomcat 启动的时候, 会初始化一个Catalina的实例。
Catalina 各个组件的职责:
Container 结构
Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。这4种容器不是平 行关系,而是父子关系。, Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵 活性。
各个组件的含义 :
我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解。Tomcat 采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件 按照一定的格式要求配置在这个顶层容器中。
那么,Tomcat是怎么管理这些容器的呢?
你会发现这些容器具有父子关系,形成一个树 形结构,你可能马上就想到了设计模式中的组合模式。
没错,Tomcat就是用组合模式来 管理这些容器的。
具体实现方法是,所有容器组件都实现了Container接口,因此组合模 式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的 是最底层的Wrapper,组合容器对象指的是上面的Context、Host或者Engine。
Container 接口中提供了以下方法(截图中知识一部分方法) :
在上面的接口看到了getParent、SetParent、addChild和removeChild等方法。
Container接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期,后 面我也用专门的篇幅去详细介绍。
Tomcat 启动流程
流程
步骤:
1) 启动tomcat , 需要调用 bin/startup.bat (在linux 目录下 , 需要调用 bin/startup.sh) , 在startup.bat 脚本中, 调用了catalina.bat。
2) 在catalina.bat 脚本文件中,调用了BootStrap 中的main方法。
3)在BootStrap 的main 方法中调用了 init 方法 , 来创建Catalina 及 初始化类加载器。
4)在BootStrap 的main 方法中调用了 load 方法 , 在其中又调用了Catalina的load方 法。
5)在Catalina 的load 方法中 , 需要进行一些初始化的工作, 并需要构造Digester 对象, 用 于解析 XML。
6) 然后在调用后续组件的初始化操作 。。。
加载Tomcat的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求 。
源码解析
Lifecycle
由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特 性, 所以Tomcat在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组 件 Server、Service、Container、Executor、Connector 组件 , 都实现了一个生命周期 的接口,从而具有了以下生命周期中的核心方法:
1) init():初始化组件
2) start():启动组件
3) stop():停止组件
4) destroy():销毁组件
各组件的默认实现
上面我们提到的Server、Service、Engine、Host、Context都是接口, 下图中罗列了这 些接口的默认实现类。
当前对于 Endpoint组件来说,在Tomcat中没有对应的Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint、 Nio2Endpoint、AprEndpoint , 这三个实现类,分别对应于前面讲解链接器 Coyote 时, 提到的链接器支持的三种IO模型:NIO,NIO2,APR , Tomcat8.5版本中,默认采 用的是 NioEndpoint。
ProtocolHandler : Coyote协议接口,通过封装Endpoint和Processor , 实现针对具体 协议的处理功能。Tomcat按照协议和IO提供了6个实现类。
AJP协议:
1) AjpNioProtocol :采用NIO的IO模型。
2) AjpNio2Protocol:采用NIO2的IO模型。
3) AjpAprProtocol :采用APR的IO模型,需要依赖于APR库
HTTP协议:
1) Http11NioProtocol :采用NIO的IO模型,默认使用的协议(如果服务器没有安装 APR)。
2) Http11Nio2Protocol:采用NIO2的IO模型。
3) Http11AprProtocol :采用APR的IO模型,需要依赖于APR库。
init源码流程追踪
在进行源码追踪前,建议大家先回顾一些组合模式和模板方法模式,因为tomcat的初始化流程主要就利用了这两种模式
组合模式
模板方法模式
1.Tomcat启动入口
1.1main函数
代码语言:javascript复制public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
//实例化一个BootStrap对象
Bootstrap bootstrap = new Bootstrap();
try {
//开始初始化BootStrap对象
//其中包括初始化三大类加载器
//初始化Catalina实例,并设置为静态变量(初始化Catalina实例,设置shared类加载器)
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
//将静态变量daemon设置为初始化完成的BootStrap实例
//确定在Tomcat关闭时关闭的是同一个Tomcat实例
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
//根据不同的启动方法进行加载
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
//实际调用为Catalina实例的load方法,通过反射调用.
daemon.load(args);
//实际调用为Catalina实例的start方法,通过反射调用.
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
//实际调用为Catalina实例的load方法,通过反射调用.
daemon.load(args);
//实际调用为Catalina实例的start方法,通过反射调用.
daemon.start();
//当Catalina实例获取失败时,系统推出
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command "" command "" does not exist.");
}
} catch (Throwable t) {
//通过抓取异常推出程序
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
1.1.1bootstrap实例的init方法
代码语言:javascript复制public void init() throws Exception {
//实例化三大类加载器
initClassLoaders();
//设置线程上下文加载器
//Catalina加载器负责加载Tomcat需要的jar包
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
//准备初始化Catalina实例
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
//反射设置shared类加载器,调用方法为setParentClassLoader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//初始化完成,将静态变量设置为初始化完成的Catalina实例对象.
catalinaDaemon = startupInstance;
}
1.1.2 反射调用Catalina实例的load方法
代码语言:javascript复制private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " method);
}
method.invoke(catalinaDaemon, param);
}
2.Catalina实例的load方法
2.1解析server.xml或server-embed.xml文件
代码语言:javascript复制if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
//初始化用于解析server.xml的类
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
//尝试获取conf/server.xml
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
//在获取conf/server.xml失败时
//获取server-embed.xml
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
//在两大xml文件都没有的情况下,直接返回
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
//通过server.xml配置server实例
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " getConfigFile() ": "
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " getConfigFile() ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
2.2 初始化Server实例
代码语言:javascript复制 getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
// 初始化Server实例,注意:在Server实例中没有init方法
// 但是在Server的父类LifecycleMBeanBase的父类LifecycleBase中存在init方法
// 同时在LifecycleBase中调用了initInternal()抽象方法,该方法由StandardServer子类实现
// 所以实际上init方法调用的为initInternal方法
//与后期的start方法同理
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " ((t2 - t1) / 1000000) " ms");
}
2.2.1 getServer().init() —> StandardServer.initInternal()
代码语言:javascript复制protected void initInternal() throws LifecycleException {
super.initInternal();
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
//
onameStringCache = register(new StringCache(), "type=StringCache");
//注册一个Bean工厂,管理server
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
// 初始化全局资源
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();//shared类加载器
//加载可以被分享的jar包
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
// ? ? ?如果不能被加载,则向上级加载器加载
cl = cl.getParent();
}
}
// 初始化service的集合
for (Service service : services) {
//代码跳转至StandardService类的initInternal方法中
service.init();
}
1.Service初始化 —>StandardService.initInternal()
代码语言:javascript复制protected void initInternal() throws LifecycleException {
//调用父类的initInternal
super.initInternal();
//engine是Tomcat的顶级容器
if (engine != null) {
engine.init();
}
//初始化线程池
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
//线程池会将子容器的初始化工作交给线程完成
executor.init();
}
// 初始化映射监听
mapperListener.init();
// 初始化连接器
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
1.1 Engine初始化 —> StandardEngine.initInternal()
代码语言:javascript复制protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();//权限管理控制
super.initInternal(); //线程池的初始化
}
1.1.1 StartStopExecutor初始化 —> StandardEngine父类ContainerBase.initInternal()
代码语言:javascript复制protected void initInternal() throws LifecycleException {
//engine实际的init方法
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
//将子容器开始或者停止的任务放入该线程池
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
1.2 Connector初始化 —> Connector.initInternal()
1.2.1 CoyoteAdpater适配器初始化
代码语言:javascript复制protected void initInternal() throws LifecycleException {
super.initInternal();
// 初始化适配器
// 适配器作用:完成客服端请求到容器的映射 N个connector到一个container
// 用于Coyote的Request,Response和HttpServlet的Request和Response适配
adapter = new CoyoteAdapter(this);
//用于统领EndPoint以及Processor
//EndPoint负责Socket监听任务
//Processor负责处理请求
//设置适配器适配请求(由Coyote的Request以及Response转换为HttpServlet的Request以及Response)
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
getProtocolHandlerClassName()));
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
//调度AbstractProtocol的init方法
//在此之间初始化Endpoint
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
1.2.2 ProtocolHandler初始化 —> AbstractProtocol.init()
代码语言:javascript复制public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
//完成jmx
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain ":type=GlobalRequestProcessor,name=" getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
//初始化EndPoint
endpoint.init();
}
1.2.3 Enpoint.bind() —> Enpoint.init()调用NioEndpoint.bind()
代码语言:javascript复制public void bind() throws Exception {
//实例化ServerSocketChannel,并绑定端口和地址
if (!getUseInheritedChannel()) {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
//设置最大连接数量
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
// 初始化accepter以及poller的数量
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// 有必要初始化ssl
initialiseSsl();
// 开启selector
selectorPool.open();
}
start源码追踪
1. BootStrap.start() —> Catalina.start()
代码语言:javascript复制public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
代码语言:javascript复制public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
//启动Server实例,注意:在Server实例中没有start方法
// 但是在Server的父类LifecycleMBeanBase的父类LifecycleBase中存在start方法
// 同时在LifecycleBase中调用了startInternal抽象方法,该方法由StandardServer子类实现
//所以实际上start方法调用的为startInternal方法
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " ((t2 - t1) / 1000000) " ms");
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
2. Server.start() —> StandardServer.startInternal()
代码语言:javascript复制protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// 启动所有services
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
3. Service.start() —> StandardService.startInternal()
代码语言:javascript复制protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// 启动Engine引擎
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
//启动线程池
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
//启动映射
mapperListener.start();
// 启动连接器
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
//调用连接器的start方法,启动会初始化ProtocolHandler
//ProtocolHandler会启动EndPoint进行监听
//实际调用为Connector.startInternal()
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
4. Engine.start() —>StandardEngine.startInternal()
代码语言:javascript复制protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " ServerInfo.getServerInfo());
// 启用父类的启动方法
super.startInternal();
}
代码语言:javascript复制protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
//获取所有子容器,并交给线程池进行启动(线程池在Engine初始化阶段以及被初始化)
//实际上在StartChild线程启动时,启动子容器的startInternal方法
//这里的子容器默认只有一个StandardHost,因此这里Host的启动是另外的线程来处理的,因此调试的时候需要注意线程切换来观察
//如果有多个容器,可以并行初始化
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
//提交HOST的初始化任务
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
//这里是等所有子容器初始化完毕,再继续往下运行,达成一种同步的效果
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
//设置管线
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//***激发STARTING状态的监听
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}
在监听激活后,HostConfig会开始加载web.xml文件,并构建Context存入Host容器中.
解释一下Host初始化代码被调用的地方
代码语言:javascript复制 private static class StartChild implements Callable<Void> {
private Container child;
//传入的就是Host
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
//线程池中挑选一个线程来对host进行初始化
child.start();
return null;
}
}
5. Host.start() —> StandardHost.startInternal()
代码语言:javascript复制protected synchronized void startInternal() throws LifecycleException {
//设置用于报告错误的Valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
//在初始化的过程中并没有初始化Context以及以下的容器
//继续通过containerBase启动子容器,所以在StandardHost中是没有设置Context的代码的
//实际代码在监听器HostConfig中,即HostConfig.deployApps()
//在deployApps中会将Context设置到对应的Host主机中
super.startInternal();
}
5.1 HostConfig.start()
ContainerBase是所有组件的公共父类
因此上面Host调用父类startInternal方法,还是会回到一开始Engine执行startInternal的方法,这一点不要搞混了
代码语言:javascript复制super.startInternal();
这里重点在setState方法
代码语言:javascript复制 //设置管线
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//***激发STARTING状态的监听
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
上面调用的start方法就是下面呈现的:
代码语言:javascript复制public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() ":type=Deployer,host=" host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.warn(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!host.getAppBaseFile().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
//启动context,部署项目
if (host.getDeployOnStartup())
deployApps();
}
代码语言:javascript复制protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// xml文件部署
deployDescriptors(configBase, configBase.list());
// war包部署
deployWARs(appBase, filteredAppPaths);
// 文件夹部署
deployDirectories(appBase, filteredAppPaths);
}
6. StandardContext.startInternal()
host下面默认有一个context,context的初始化方法是在该方法中被调用的:
代码语言:javascript复制 // 文件夹部署
deployDirectories(appBase, filteredAppPaths);
我们可以追踪看一下
代码语言:javascript复制 /**
* Deploy exploded webapps.
* @param appBase The base path for applications
* @param files The exploded webapps that should be deployed
*/
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
for (int i = 0; i < files.length; i ) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
//执行一个任务
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
for (Future<?> result : results) {
try {
//阻塞等待任务执行完毕
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
重点在这里:
代码语言:javascript复制 //执行一个任务
results.add(es.submit(new DeployDirectory(this, cn, dir)));
代码语言:javascript复制 /**
* Deploy exploded webapp.
* @param cn The context name
* @param dir The path to the root folder of the weapp
*/
protected void deployDirectory(ContextName cn, File dir) {
...
Context context = null;
File xml = new File(dir, Constants.ApplicationContextXml);
File xmlCopy =
new File(host.getConfigBaseFile(), cn.getBaseName() ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = isCopyXML();
boolean deployThisXML = isDeployThisXML(dir, cn);
try {
if (deployThisXML && xml.exists()) {
synchronized (digesterLock) {
try {
context = (Context) digester.parse(xml);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
xml), e);
context = new FailedContext();
} finally {
digester.reset();
if (context == null) {
context = new FailedContext();
}
}
}
if (copyThisXml == false && context instanceof StandardContext) {
// Host is using default value. Context may override it.
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (copyThisXml) {
Files.copy(xml.toPath(), xmlCopy.toPath());
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} else if (!deployThisXML && xml.exists()) {
// Block deployment as META-INF/context.xml may contain security
// configuration necessary for a secure deployment.
log.error(sm.getString("hostConfig.deployDescriptor.blocked",
cn.getPath(), xml, xmlCopy));
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).getConstructor().newInstance();
}
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
//重点在这一行
host.addChild(context);
}
....
}
回到父类Host的addChild中
代码语言:javascript复制 /**
* Add a child Container, only if the proposed child is an implementation
* of Context.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
....
child.addLifecycleListener(new MemoryLeakTrackingListener());
// Avoid NPE for case where Context is defined in server.xml with only a
// docBase
Context context = (Context) child;
if (context.getPath() == null) {
ContextName cn = new ContextName(context.getDocBase(), true);
context.setPath(cn.getPath());
}
//重点
super.addChild(child);
}
父类是ContainerBase
代码语言:javascript复制 public void addChild(Container child) {
...
else{
addChildInternal(child);
}
}
private void addChildInternal(Container child) {
...
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '"
child.getName()
"' is not unique");
child.setParent(this); // May throw IAE
//当前Host容器下放入一个Context子容器
children.put(child.getName(), child);
}
// Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
//启动context子容器
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
}
最终调用context的startInternal方法
代码语言:javascript复制protected synchronized void startInternal() throws LifecycleException {
if(log.isDebugEnabled())
log.debug("Starting " getBaseName());
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
setConfigured(false);
boolean ok = true;
// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
}
// 创建工作目录
postWorkDirectory();
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
//web应用加载器
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader();
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
//cookie处理器
// An explicit cookie processor hasn't been specified; use the default
if (cookieProcessor == null) {
cookieProcessor = new Rfc6265CookieProcessor();
}
// Initialize character set mapper
getCharsetMapper();
// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
}
if (!dependencyCheck) {
// do not make application available if dependency check fails
ok = false;
}
// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}
if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}
// Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup");
// Binding thread
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
// since the loader just started, the webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("clearReferencesThreadLocals",
getClearReferencesThreadLocals());
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
Realm realm = getRealmInternal();
if(null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
}
@Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
}
// Notify our interested LifecycleListeners
// 发布生命周期时间,即开始加载ContextConfig中的
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// 如果子容器没有启动,则开启子容器,即循环启动wrapper对象
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// Start the Valves in our pipeline (including the basic),
// if any
// 开启管线
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ((getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error("standardContext.clusterFail", ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
// We put the resources into the servlet context
if (ok) {
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
// Create context attributes that will be required
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
// Make the version info available
getServletContext().setAttribute(Globals.WEBAPP_VERSION, getWebappVersion());
}
// Set up the context init params
mergeParameters();
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
// Send j2ee.object.failed notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.object.failed",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
} else {
setState(LifecycleState.STARTING);
}
}
6.1 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null) —> Context.configureStart()
代码语言:javascript复制protected synchronized void configureStart() {
...
//该方法比较重要,负责解析web.xml
webConfig();
context.addServletContainerInitializer(new JasperInitializer(),null);
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
....
}
6.2 webConfig() —> processClasses(webXml, orderedFragments)
webConfig方法涉及到web.xml文件的解析和注解的解析流程,下面给出的方法只是解析注解的
代码语言:javascript复制protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {
// Step 4. Process /WEB-INF/classes for annotations and
// @HandlesTypes matches
Map<String, JavaClassCacheEntry> javaClassCache = new HashMap<>();
if (ok) {
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
//通过注解解析Servlet,Filter,Listener
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete(), javaClassCache);
}
}
7. StandardWrapper.startInternal()
这里的StandardWrapper就比较接近面向用户的servlet了,快到头了,再坚持一下
这里StandardWrapper的startInternal方法被调用流程和上面Engine部署文件夹时,解析完context.xml后,添加子容器host时,host的初始化被调用方法类似的思路,下面我们来看一下
题外话:在当前构建Context的方法中,我们也可以看出webXml解析的时候就可以获取到当前Host容器下面所有servlet
Host下面的child子容器肯定就是Wrapper了
到这里就已经和上面合起来了,这里父类最终调用addInternal方法:
最终挨个调用当前Context下每个Wrapper的初始化方法:
代码语言:javascript复制protected synchronized void startInternal() throws LifecycleException {
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(),
sequenceNumber );
broadcaster.sendNotification(notification);
}
// Start up this component
super.startInternal();
setAvailable(0L);
// Send j2ee.state.running notification
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber );
broadcaster.sendNotification(notification);
}
}
wrapper下面就无子容器了
8. Connector.startInternal()
上面只是分析完了service组件中第一个engine的start流程,下面开始第二个组件Connector
代码语言:javascript复制 protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
//启动ProtocolHandler
//调用AbstractProtocol.start()
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
9.ProtocolHandler.start() —> AbstractProtocol.start()
代码语言:javascript复制public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
//启动Endpoint开始监听
endpoint.start();
// Start timeout thread
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
10.Endpoint.start() —> NioEndpoint.startInternal()
代码语言:javascript复制public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
initializeConnectionLatch();
// 开启poller线程
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i ) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() "-ClientPoller-" i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
//开启acceptor线程
startAcceptorThreads();
}
Poller.run()
当接收客户端请求时,NioEndpoint.Poller会获取事件并开始迭代执行.(一下省略部分代码)
代码语言:javascript复制public void run() {
while (true) {
.......
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
iterator.remove();
//开始正式执行请求流程
processKey(sk, attachment);
}
}//while
//process timeouts
timeout(keyCount,hasEvents);
}//while
getStopLatch().countDown();
}
Poller.processKey()
processKey方法继续调用其中的processSocket方法,并开始处理会话.
代码语言:javascript复制protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
try {
if ( close ) {
cancelledKey(sk);
} else if ( sk.isValid() && attachment != null ) {
if (sk.isReadable() || sk.isWritable() ) {
if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment, false);
} else {
unreg(sk, attachment, sk.readyOps());
boolean closeSocket = false;
// Read goes before write
if (sk.isReadable()) {
//开始处理会话
//处理写事件
if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {
closeSocket = true;
}
}
//处理写事件
if (!closeSocket && sk.isWritable()) {
if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {
closeSocket = true;
}
}
if (closeSocket) {
cancelledKey(sk);
}
}
}
} else {
//invalid key
cancelledKey(sk);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("",t);
}
}
AbstractEndpoint.processSocket()
当跟踪进processSocket时会发现调用的是AbstractEndpoint的processSocket方法.
他会首先获取Socket的处理器,并获取线程池为处理Socket单独开启一个线程.
代码语言:javascript复制public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
try {
if (socketWrapper == null) {
return false;
}
//获取socket的处理器
SocketProcessorBase<S> sc = processorCache.pop();
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
//获取到线程池
Executor executor = getExecutor();
if (dispatch && executor != null) {
//由线程池调用一个线程来执行Socket处理器
executor.execute(sc);
} else {
sc.run();
}
} catch (RejectedExecutionException ree) {
getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
getLog().error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
NioEndpoint.SocketProcessor.doRun()
经过跟踪会最终发现,sc.run()方法实际上是在调用NioEndpoint.SocketProcessor.doRun();
代码语言:javascript复制 @Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
try {
int handshake = -1;
try {
if (key != null) {
if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
//获取Handler处理器:Servlet(处理器),调度处理器的处理方法
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key);
}
} else if (handshake == -1 ) {
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error("", t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this);
}
}
}
}
AbstractProtocol.ConnectionHandler.process
在doRun的方法中最后会交给AbstractProtocol.ConnectionHandler.process方法执行.
代码语言:javascript复制@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString("abstractConnectionHandler.process",
wrapper.getSocket(), status));
}
if (wrapper == null) {
// Nothing to do. Socket has been closed.
return SocketState.CLOSED;
}
//获取Socket
S socket = wrapper.getSocket();
//交给Processor处理 -> 真正开始处理请求
Processor processor = connections.get(socket);
....
do {
//开始解析请求
state = processor.process(wrapper, status);
.....
}
} while ( state == SocketState.UPGRADING);
}
至此,请求由Endpoint组件正式转交给Processor进行处理.
startAcceptorThreads
代码语言:javascript复制protected final void startAcceptorThreads() {
//根据初始化时设置的值开启accepter监听
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i ) {
//模板方法,创建一个accepter实例
acceptors[i] = createAcceptor();
String threadName = getName() "-Acceptor-" i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
createAcceptor()—>acceptor---->run
代码语言:javascript复制 // --------------------------------------------------- Acceptor Inner Class
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run()
int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
countUpOrAwaitConnection();
SocketChannel socket = null;
try {
// Accept the next incoming connection from the server
// socket
//等待接收客户端连接
socket = serverSock.accept();
...
}
...
}
总结
从启动流程图中以及源码中,我们可以看出Tomcat的启动过程非常标准化, 统一按照生 命周期管理接口Lifecycle的定义进行启动。首先调用init() 方法进行组件的逐级初始化操 作,然后再调用start()方法进行启动。
每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法, 组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换。