Tomcat卷一 ----架构和初始化源码分析

2022-05-09 13:56:53 浏览数 (1)

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源码追踪
          1. BootStrap.start() ---> Catalina.start()
          1. Server.start() ---> StandardServer.startInternal()
          1. Service.start() ---> StandardService.startInternal()
          1. Engine.start() --->StandardEngine.startInternal()
          1. Host.start() ---> StandardHost.startInternal()
          • 5.1 HostConfig.start()
          1. StandardContext.startInternal()
          • 6.1 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null) ---> Context.configureStart()
          • 6.2 webConfig() ---> processClasses(webXml, orderedFragments)
          1. StandardWrapper.startInternal()
          1. 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解析器初始化:

代码语言:javascript复制
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,这是 适配器模式的经典运用,连接器调用CoyoteAdapterSevice方法,传入的是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()方法进行启动。

每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法, 组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换。

0 人点赞