利刃出鞘_Tomcat 核心原理解析(三)
一、 Tomcat专题 - Tomcat架构 - 启动流程
1、Tomcat 启动流程
2、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 的配置文件,初始化容器组件 ,监听对应的端口号, 准备接受客户端请求。
二、 Tomcat专题 - Tomcat架构 - 启动流程 - 涉及组件介绍
1、源码解析 Lifecycle
由于所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性, 所以 Tomcat 在设计的时候, 基于生命周期管理抽象成了一个接口 Lifecycle ,而组件 Server、Service、Container、Executor、Connector 组件 , 都实现了一个生命周期的接口,从而具有了以下生命周期中的核心方法:
1) init():初始化组件。
2) start():启动组件。
3) stop():停止组件。
4) destroy():销毁组件。
2、各组件的默认实现
Server、Service、Engine、Host、Context 都是接口, 下图中罗列了这些接口的默认实现类。当前对于 Endpoint 组件来说,在 Tomcat 中没有对应的 Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint、Nio2Endpoint、AprEndpoint ,这三个实现类,分别对应于前面讲解链接器 Coyote 时, 提到的链接器支持的三种IO模型:NIO,NIO2,APR , Tomcat8.5版本中,默认采用的是 NioEndpoint。
3、ProtocolHandler : Coyote 协议接口,通过封装 Endpoint和Processor , 实现针对具体
协议的处理功能。Tomcat 按照协议和 IO 提供了6个实现类。
- AJP协议:
1) AjpNioProtocol :采用NIO的IO模型。
2) AjpNio2Protocol:采用NIO2的IO模型。
3) AjpAprProtocol :采用APR的IO模型,需要依赖于APR库。
H- TTP协议:
1) Http11NioProtocol :采用NIO的IO模型,默认使用的协议(如果服务器没有安装
APR)。
2) Http11Nio2Protocol:采用NIO2的IO模型。
3) Http11AprProtocol :采用APR的IO模型,需要依赖于APR库。
三、 Tomcat专题 - Tomcat架构 - 启动流程 - 源码跟踪
1、tomcat 源码入口 类
目录: org.apache.catalina.startup.java
2、 tomcat 源码入口 main() 方法
MainClass:BootStrap ‐‐‐‐> main(String[] args)
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {
System.out.println("main");
if (daemon == null) {
System.out.println("main if");
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
System.out.println("main 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";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
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);
}
}
四、 Tomcat专题 - Tomcat架构 - 启动流程 - 跟踪源码 - Debug
1、从启动流程图中以及源码中,我们可以看出Tomcat的启动过程非常标准化, 统一按照生命周期管理接口Lifecycle的定义进行启动。首先调用init() 方法进行组件的逐级初始化操作,然后再调用start()方法进行启动。
2、每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法, 组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换。
五、 Tomcat专题 - Tomcat架构 - 请求处理流程
1、 Tomcat 请求流程
1)设计了这么多层次的容器,Tomcat 是怎么确定每一个请求应该由哪个 Wrapper 容器里的 Servlet 来处理的呢?
答案是,Tomcat 是用 Mapper 组件来完成这个任务的。
2)Mapper 组件的功能就是将用户请求的 URL 定位到一个 Servlet,它的工作原理是:
Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,
比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及 Wrapper 容器里
Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map。
3)当一个请求到来时,Mapper 组件通过解析请求 URL 里的域名和路径,再到自己保存的
Map 里去查找,就能定位到一个 Servlet。请你注意,一个请求URL最后只会定位到一个
Wrapper 容器,也就是一个 Servlet。
2、下面的示意图中 , 就描述了 当用户请求链接 http://www.itcast.cn/bbs/findAll 之
后, 是如何找到最终处理业务逻辑的 servlet 。
3、那上面这幅图只是描述了根据请求的URL如何查找到需要执行的Servlet , 那么下面我们
再来解析一下 , 从Tomcat的设计架构层面来分析Tomcat的请求处理。
4、步骤如下:
1) Connector组件Endpoint中的Acceptor监听客户端套接字连接并接收Socket。
2) 将连接交给线程池Executor处理,开始执行请求响应任务。
3) Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
4) Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
5) CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应对象Response传递到Engine容器中,调用 Pipeline。
6) Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的 Valve–StandardEngineValve,负责调用Host容器的Pipeline。
7) Host容器的管道开始处理,流程类似,最后执行 Context容器的Pipeline。
8) Context容器的管道开始处理,流程类似,最后执行 Wrapper容器的Pipeline。
9) Wrapper容器的管道开始处理,流程类似,最后执行 Wrapper容器对应的Servlet对象的 处理方法。
六、 Tomcat专题 - Tomcat架构 - 请求处理流程 - 源码跟踪
1、请求流程源码解析
2、在前面所讲解的Tomcat的整体架构中,我们发现Tomcat中的各个组件各司其职,组件之间松耦合,确保了整体架构的可伸缩性和可拓展性,那么在组件内部,如何增强组件的灵活性和拓展性呢?
在 Tomcat 中,每个 Container 组件采用责任链模式来完成具体的请求处理。
3、在 Tomcat 中定义了 Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链, 后者代表责任链上的每个处理器。Pipeline 中维护了一个基础的Valve,它始终位于 Pipeline 的末端(最后执行),封装了具体的请求处理和输出响应的过程。
当然,我们也可以调用 addValve()方法, 为 Pipeline 添加其他的 Valve, 后添加的 Valve 位于基础的 Valve 之前,并按照添加顺序执行。Pipiline 通过获得首个Valve来启动整合链条的执行 。
上一节关联链接请点击
# 利刃出鞘_Tomcat 核心原理解析(二)