• # 利刃出鞘_Tomcat 核心原理解析(三)


    利刃出鞘_Tomcat 核心原理解析(三)

    一、 Tomcat专题 - Tomcat架构 - 启动流程

    1、Tomcat 启动流程

    tomcat-19.png

    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():销毁组件。

    tomcat-20.png

    2、各组件的默认实现

    Server、Service、Engine、Host、Context 都是接口, 下图中罗列了这些接口的默认实现类。当前对于 Endpoint 组件来说,在 Tomcat 中没有对应的 Endpoint 接口, 但是有一个抽象类 AbstractEndpoint ,其下有三个实现类: NioEndpoint、Nio2Endpoint、AprEndpoint ,这三个实现类,分别对应于前面讲解链接器 Coyote 时, 提到的链接器支持的三种IO模型:NIO,NIO2,APR , Tomcat8.5版本中,默认采用的是 NioEndpoint。

    tomcat-21.png

    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-22.png

    三、 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 。

    tomcat-23.png

    3、那上面这幅图只是描述了根据请求的URL如何查找到需要执行的Servlet , 那么下面我们

    再来解析一下 , 从Tomcat的设计架构层面来分析Tomcat的请求处理。

    tomcat-24.png

    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、请求流程源码解析

    tomcat-25.png

    2、在前面所讲解的Tomcat的整体架构中,我们发现Tomcat中的各个组件各司其职,组件之间松耦合,确保了整体架构的可伸缩性和可拓展性,那么在组件内部,如何增强组件的灵活性和拓展性呢?

    在 Tomcat 中,每个 Container 组件采用责任链模式来完成具体的请求处理。

    3、在 Tomcat 中定义了 Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链, 后者代表责任链上的每个处理器。Pipeline 中维护了一个基础的Valve,它始终位于 Pipeline 的末端(最后执行),封装了具体的请求处理和输出响应的过程。

    当然,我们也可以调用 addValve()方法, 为 Pipeline 添加其他的 Valve, 后添加的 Valve 位于基础的 Valve 之前,并按照添加顺序执行。Pipiline 通过获得首个Valve来启动整合链条的执行 。

    上一节关联链接请点击
    # 利刃出鞘_Tomcat 核心原理解析(二)

  • 相关阅读:
    第十五届蓝桥杯省赛第二场C/C++B组A题【进制】题解(AC)
    如何安装HTMLTestRunner?
    JVM之垃圾判断的详细解析
    Fastdfs之多机房同步模拟测试
    java在Windows配置Path环境变量
    暴力求解欲哭无泪之保安问题
    常用 的 Vue3 新增方法及用法
    快速掌握Zookeeper及Java API
    Window Server2008配置https
    医药、护士资格考试题库小程序使用指引
  • 原文地址:https://blog.csdn.net/qfyh_djh/article/details/141066102