Tomcat核心流程

一、Tomcat的架构

1.1 架构简介

Tomcat总体架构设计的非常优雅。

首先一个Tomcat服务器实例代表一个Server,Server负责接收请求和处理请求并返回结果。Tomcat这地方将请求的接收和处理进行了解耦,每个Server分为Connector和Container两部分,Connector可以有N个,分别负责不同的协议。

同时每个Server可以部署多个项目,绑定多个端口,所以将Connector和Container封装为一个Service。

每个Service包含的Connector监听一个端口,负责处理该端口不同协议的请求,将不同协议的请求转化为统一的request。而Container每个Service只能持有一个,负责request的处理。(每个Connector绑定一个端口,不同Service绑定的端口不同,例如ServiceA可能持有两个Connector,分别绑定8080和8081,ServiceB持有一个Connector,绑定8083)

每个Container实际为一个Engine,每个Engine可以包含N个Host,对应不同的ip或者域名,每个Host包含N个Context,每个Context表示一个具体的Web应用,而每个Context包含N个Wrapper,每个Wrapper封装一个Servlet。

例如:www.aaa.com和www.bbb.com两个域名的请求都交给同一个Tomcat处理,则www.aaa.com:8080/test和www.bbb.com:8080/test就会交给绑定了8080这个端口的Service,然后根据www.aaa.com和www.bbb.com分别寻找Container中对应的Host,然后再从Host下分别找test这个Context。

各个组件的嵌套关系类似下图
在这里插入图片描述
在这里插入图片描述

1.2 生命周期

同时,所有组件又都实现了顶层的接口Lifecycle,即生命周期管理接口,该接口有以下几个方法:

  • init():初始化组件
  • start():启动组件
  • stop():停止组件
  • destory():销毁组件

每个生命周期都可能对应几个状态的转换,可以为组件添加事件监听器(LifecycleListener)用于监听组件的状态变化。

1.3 Connector

Connector的主要职责:

  1. 监听端口
  2. 将客户请求按照指定协议解析
  3. 根据请求路径匹配Mapper,得到处理该请求的容器
  4. 将请求交给正确容器处理
  5. 返回结果

Connector持有一个ProtocolHandler,负责处理具体的协议,例如Http11NioProtocol表示基于NIO的Http 1.1协议处理器。Protocol又包含一个Endpoint负责启动Socket绑定端口并监听,和一个Processor负责按指定协议分析请求数据,并根据请求路径匹配具体的容器。

Processor是如何匹配容器的?这是根据Mapper来匹配的,Mapper负责维护容器映射信息,同时按照映射规则查找容器。

Mapper又是如何动态维护容器 的呢?通过MapperListener,MapperListener类实现了ContainerListener, LifecycleListener接口,可以在容器组件状态发生变化的时候,动态地注册或者取消容器组件的映射信息。(该组件由Service持有,监听该Service下所有的容器组件)

1.4 Pipeline和Value

为了每个Container组件的可扩展性,Tomcat的每个组件都采用了职责链的模式,即每个组件都持有一个Pipeline和一个Value,Pipeline负责构建Value链,Value负责进行请求的处理。

1.5 总体架构图

在这里插入图片描述
在这里插入图片描述
从图中可以看出,只论Tomcat的话,Tomcat从Bootstrap类main方法启动,该类会创建一个Catalina实例,Catalina持有一个Server的引用。Server持有所有Service引用,Service持有各自所有的Connector、Mapper、MapperListener和Container(Engine)的引用,MapperListener持有Mapper方便监听事件发生后动态更改Mapper。

1.6 WebAppClassLoader

每个web应用都有自己所属的web应用类加载器实例,作用是隔离各个web应用,提高了灵活性,让各个web应用互不影响。

二、Tomcat启动流程

2.1 流程图

在这里插入图片描述

2.2 流程

  1. 执行bootstrap的main方法。该方法创建一个Bootstrap对象,调用init()方法。init方法初始化一个ClassLoader,并通过ClassLoader创建一个Catalina对象,赋给catalinaDemon变量。

  2. Catalina解析conf/server.xml,借助封装的Digester XML解析框架解析该XML文件,该框架会形成一个栈,然后创建完一个对象并全部设置好以后,即遇到end()方法会将对象出栈,Sever对象在栈底,当其出栈,整个Tomcat就设置完毕了。

    1. 创建Server对象并且添加生命周期监听器。
    2. 创建Service实例,添加Service的工作线程池
    3. 为Service添加生命周期监听器(默认无监听该组件的监听器),注意Service对象创建出来时就包含Mapper和MapperListener,其中将this传入MapperListener,让其实现对自己包含容器组件进行监听,具体的监听见server.start()这个步骤。
    4. 为Service添加Connector,调用service的addConnector,在添加Connector之前会先给Connector添加生命周期监听(默认无该组件监听器)
    5. 创建Engine实例,并为Engine添加集群配置、生命周期监听(HostConfig类)、 添加安全配置(设置默认Value)等等
    6. 创建Host实例,为Host添加集群配置、生命周期监听(HostConfig类)、添加安全设置(设置默认Value等等)
    7. 解析server.xml中的Context配置。(Context加载有2个入口,在解析server.xml构造server时通过start()方法构建是其中之一。另一个是HostConfig这个监听器来自动扫描部署目录,该方式包括3种具体的部署形式:①Context.xml部署 ② web目录部署 ③war包部署)。默认server.xml是不设置context的,因为这样很麻烦,每次web应用的改动都需要到tomcat安装目录下改动server.xml。
    8. Context解析主要的工作:为Context添加生命周期监听(具体类由< context>标签中的classname属性指定)、为Context指定类加载器(用于隔离web应用)、添加会话管理器、添加初始化参数、添加安全配置以及静态资源配置、为Context添加默认的拦截器Value(Spring MVC框架的入口)、为Context添加守护资源和Cookie处理器。
  3. 完成上述步骤后,Server对象已经创建完成,调用其init()方法,然后逐层调用子组件的init()方法完成初始化 (包括MapperListener的初始化)。

  4. 调用server的start()方法,逐层调用子组件的start()方法。当执行到Host的start()方法时会发出START_EVENT事件,由Host持有的HostConfig生命周期监听器感知到并处理。(HostConfig负责处理的生命周期事件包括:START_EVENT、PERIODIC_EVENT、STOP_EVENT,前两者与web应用部署相关,其中PERIODIC_EVENT负责守护资源的监控,当守护资源发生变化时,重新部署web,STOP_EVENT在Host停止时注销其对应的MBean)

    注意:在Service.start()时会调用MapperListener的start(),此时会根据MappListener持有的this指针,得到Engine,然后获取到所有的子Container,循环将这些子组件注册到MapperListener上,这样每个子组件调用addChild、removeChild等方法时,MapperListener可以感知到,然后动态维护容器组件的映射。
    (此时web.xml已经解析完毕,Host、一些使用web.xml配置的context都已经解析完毕了,通过其余方式配置的context属于动态添加的,MapperListener通过addChild事件感知,并动态添加到Mapper中)

  5. HostConfig感知到START_EVENT事件后,会依次执行deployDescriptors()deployWARs()deployDirectories()方法完成按照context.xml文件部署web应用、按照war包部署web、按照web目录部署。

    public void lifecycleEvent(LifecycleEvent event) {
        try {
            this.host = (Host)event.getLifecycle();
            if (this.host instanceof StandardHost) {
                this.setCopyXML(((StandardHost)this.host).isCopyXML());
                this.setDeployXML(((StandardHost)this.host).isDeployXML());
                this.setUnpackWARs(((StandardHost)this.host).isUnpackWARs());
                this.setContextClass(((StandardHost)this.host).getContextClass());
            }
        } catch (ClassCastException var3) {
            log.error(sm.getString("hostConfig.cce", new Object[]{event.getLifecycle()}), var3);
            return;
        }
    
        if (event.getType().equals("periodic")) {
            this.check();
        } else if (event.getType().equals("before_start")) {
            this.beforeStart();
            //感知到start事件
        } else if (event.getType().equals("start")) {
            this.start();
        } else if (event.getType().equals("stop")) {
            this.stop();
        }
    
    }
    --------------------------------------------
    public void start() {
       //....其他代码
        if (this.host.getDeployOnStartup()) {
            this.deployApps();
        }
     }
     -----------------------------
     protected void deployApps() {
        File appBase = this.host.getAppBaseFile();
        File configBase = this.host.getConfigBaseFile();
        String[] filteredAppPaths = this.filterAppPaths(appBase.list());
        this.deployDescriptors(configBase, configBase.list());
        this.deployWARs(appBase, filteredAppPaths);
        this.deployDirectories(appBase, filteredAppPaths);
    }
    
  6. 按照context.xml部署web应用,会根据Host的xmlBase属性指定,若未指定去默认的目录/conf/< Engine名称>/< Host名称>下寻找。对于该目录下的多个context.xml,使用线程池完成解析。

    1. 使用Digester解析配置文件,创建Context实例
    2. 添加生命周期监听器
    3. 通过Host的addChild()添加到Host中,在此过程中会判断Host是否已经启动,如果已经启动则直接启动context (调用start方法)。
    4. 将Context.xml、web应用目录、web.xml等添加到守护资源,以便文件发生变更时重新部署或者加载web应用。(文件变更通过文件现在修改时间与上次修改时间进行比较进行判断。 重新部署是当context.xml发生改变时进行,重新生成context实例,重新加载是web.xml发生改变,重启同一个context对象)
  7. 按照war包部署,对Host的appBase路径下所有符合条件的war包进行扫描,然后由线程池完成部署,过程大致同上一步

  8. 按照web目录部署,将web应用复制到Host指定appBase下即可。过程类似于前面的方式。

  9. START_EVENT的处理过程中创建完Context实例并添加到Host后,会调用start()方法(每个线程负责一个Context的创建和启动)。创建的Context默认实现来是standardContext类,其start()过程如下:

    1. 发布正在启动的JMX通知,可以通过添加NotificationListener来监听web应用的启动。(JMX是JVM提供扩展,用于监控和管理java程序和jvm)
    2. 启动当前context维护的JNDI资源,如果使用JNDI,还会添加NamingContextListener
    3. 初始化WebResourceRoot并启动,该类维护了所有的web应用资源,主要用于类加载和按照路径查找资源文件
    4. 创建web应用类加载器
    5. 设置默认的cookie处理器
    6. 设置字符集映射
    7. 初始化临时目录,然后检查依赖
    8. 启动安全组件(Realm)
    9. 发布CONFIGURE_START_EVENT事件,由该实例持有ContextConfig类监听并进行处理 (完成Servlet的创建)。(该监听器在创建Context实例时就已经被注入了,ContextConfig负责解析web.xml以及寻找Context的ServletContainerInitializer并添加到Context中。)
    10. 将WebResourceRoot添加到ServletContext中
    11. 启动Context的子节点和Pipeline,创建会话管理器
    12. 创建实例管理器,用于创建Servlet、Filter等对象
    13. 启动添加到当前Context的ServletContainerInitializer (SpringBoot在外置Tomcat中启动使用此种方法)。该类的实例由前面处理CONFIGURE_START_EVENT事件的ContextConfig查找并添加到Context中
    14. 实例化应用监听器(ApplicationListener),分为事件监听器和生命周期监听器两大类,这些监听器可以通过context.xml、web.xml、编程方式(ServletContainerInitializer)来添加到Context中,并触发其中ServletContextListenercontextInitialized()方法 (springboot在外置tomcat的启动方式,具体见后文)
    15. 启动会话管理器
    16. 实例化FilterConfig、Filter,并调用Filter.init()方法初始化
    17. 对于loadOnstartup≥0的wrapper,调用wrapper.load(),进行Servlet的实例化和初始化。(Servlet在第9步完成创建)
    18. 启动后台定时线程,监控守护资源的变更
    19. 发布正在运行的JMX通知
    20. 清理无用的资源
    21. 设置Context的状态,成功设置为STARTING,失败为FAILED
  10. Tomcat启动完毕

三、Tomcat请求处理流程

3.1 请求流程图

在这里插入图片描述

3.2 请求流程

如果MVC框架,则会添加额外的逻辑,使得最终映射到DispatcherServlet,然后由DispatcherServlet持有的HandlerMapping匹配最终的处理器。

MVC框架的请求处理逻辑网上有很多,也可以参考:

从Tomcat到SpringMVC

此处详细分析一下Tomcat收到请求是如何匹配到对应的Container,此处的核心是Mapper。Mapper、Container、Connector三者是通过CoyoteAdapter联系在一起的(适配器模式)。其中Mapper、Container被Service持有,Connector持有Service的引用,Adapter持有Connector的引用。

具体流程:

  1. Connector接收请求,读取数据,封装为一个Request对象,并创建一个Response对象,然后将Request(org.apache.coyote.Request)和Response作为参数传入CoyoteAdapter.service()
  2. service()方法根据Connector发送的 Request和Response构造创建org.apache.catalina.connector.Requestorg.apache.catalina.connector.Response
  3. 解析Request中的URI,并检查URI是否合法,不合法返回404
  4. 进行映射,将映射结果存储到Request的mappingData中。
    1. 进行映射时,首先会调用Adapter的postParseRequest()方法,该方法通过一个循环来进行匹配,每次循环中都会通过Mapper.map()方法进行匹配。(在调用前创建三个变量,version表示版本号,为null则表示匹配所有版本、versionContext暂存按照会话ID匹配的context、mapRequired表示是否继续映射,循环通过该变量控制)
    2. map()按照请求路径进行匹配。(先查找Host,再查找Context,最后查找Wrapper,匹配到的Host、Context、Wrapper都会加入到mappingData中)
  5. 进行一些判断(是否允许追踪、鉴权、认证等)
  6. 开始执行,过程如下:
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值