面试官:谈谈 Tomcat 架构及启动过程,我一脸懵逼。。

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

来源:https://github.com/c-rainstorm/blog/tree/master/tomcat

这个题目命的其实是很大的,写的时候还是很忐忑的,但我尽可能把这个过程描述清楚。因为这是读过源码以后写的总结,在写的过程中可能会忽略一些前提条件,如果有哪些比较突兀就出现,或不好理解的地方可以给我提 Issue,我会尽快补充修订相关内容。

很多东西在时序图中体现的已经非常清楚了,没有必要再一步一步的作介绍,所以本文以图为主,然后对部分内容加以简单解释。

绘制图形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension

本文对 Tomcat 的介绍以 Tomcat-9.0.0.M22 为标准。

https://tomcat.apache.org/tomcat-9.0-doc/index.html

Overview

6d800fa87e26fe9a685b2d152e997197.png
  1. Bootstrap 作为 Tomcat 对外界的启动类,在 $CATALINA_BASE/bin 目录下,它通过反射创建 Catalina 的实例并对其进行初始化及启动。

  2. Catalina 解析 $CATALINA_BASE/conf/server.xml 文件并创建 StandardServer、StandardService、StandardEngine、StandardHost 等

  3. StandardServer 代表的是整个 Servlet 容器,他包含一个或多个 StandardService

  4. StandardService 包含一个或多个 Connector,和一个 Engine,Connector 和 Engine 都是在解析 conf/server.xml 文件时创建的,Engine 在 Tomcat 的标准实现是 StandardEngine

  5. MapperListener 实现了 LifecycleListener 和 ContainerListener 接口用于监听容器事件和生命周期事件。该监听器实例监听所有的容器,包括 StandardEngine、StandardHost、StandardContext、StandardWrapper,当容器有变动时,注册容器到 Mapper。

  6. Mapper 维护了 URL 到容器的映射关系。当请求到来时会根据 Mapper 中的映射信息决定将请求映射到哪一个 Host、Context、Wrapper。

  7. Http11NioProtocol 用于处理 HTTP/1.1 的请求

  8. NioEndpoint 是连接的端点,在请求处理流程中该类是核心类,会重点介绍。

  9. CoyoteAdapter 用于将请求从 Connctor 交给 Container 处理。使 Connctor 和 Container 解耦。

  10. StandardEngine 代表的是 Servlet 引擎,用于处理 Connector 接受的 Request。包含一个或多个 Host(虚拟主机), Host 的标准实现是 StandardHost。

  11. StandardHost 代表的是虚拟主机,用于部署该虚拟主机上的应用程序。通常包含多个 Context (Context 在 Tomcat 中代表应用程序)。Context 在 Tomcat 中的标准实现是 StandardContext。

  12. StandardContext 代表一个独立的应用程序,通常包含多个 Wrapper,一个 Wrapper 容器封装了一个 Servlet,Wrapper的标准实现是 StandardWrapper。

  13. StandardPipeline 组件代表一个流水线,与 Valve(阀)结合,用于处理请求。StandardPipeline 中含有多个 Valve, 当需要处理请求时,会逐一调用 Valve 的 invoke 方法对 Request 和 Response 进行处理。特别的,其中有一个特殊的 Valve 叫 basicValve,每一个标准容器都有一个指定的 BasicValve,他们做的是最核心的工作。

  • StandardEngine 的是 StandardEngineValve,他用来将 Request 映射到指定的 Host;

  • StandardHost 的是 StandardHostValve, 他用来将 Request 映射到指定的 Context;

  • StandardContext 的是 StandardContextValve,它用来将 Request 映射到指定的 Wrapper;

  • StandardWrapper 的是 StandardWrapperValve,他用来加载 Rquest 所指定的 Servlet,并调用 Servlet 的 Service 方法。

Tomcat init

c22eca7e811faed1142510b9a65775eb.png
  1. 通过从 CatalinaProperties 类中获取 common.loader 等属性,获得类加载器的扫描仓库。CatalinaProperties 类在的静态块中调用了 loadProperties() 方法,从 conf/catalina.properties 文件中加载了属性.(即在类创建的时候属性就已经加载好了)。

  2. 通过 ClassLoaderFactory 创建 URLClassLoader 的实例

  1. EngineConfig。LifecycleListener 的实现类,触发 Engine 的生命周期事件后调用,这个监听器没有特别大的作用,就是打印一下日志

  2. HostConfig。LifecycleListener 的实现类,触发 Host 的生命周期事件后调用。这个监听器的作用就是部署应用程序,这包括 conf/// 目录下所有的 Context xml 文件 和 webapps 目录下的应用程序,不管是 war 文件还是已解压的目录。另外后台进程对应用程序的热部署也是由该监听器负责的。

  3. ContextConfig。LifecycleListener 的实现类,触发 Context 的生命周期事件时调用。这个监听器的作用是配置应用程序,它会读取并合并 conf/web.xml 和 应用程序的 web.xml,分析 /WEB-INF/classes/ 和 /WEB-INF/lib/*.jar中的 Class 文件的注解,将其中所有的 Servlet、ServletMapping、Filter、FilterMapping、Listener 都配置到 StandardContext 中,以备后期使用。当然了 web.xml 中还有一些其他的应用程序参数,最后都会一并配置到 StandardContext 中。

Tomcat Start[Deployment]

308593375a166f890128aefa8effc5a5.png
  1. 解析 $CATALINA_BASE/conf/// 目录下所有定义 Context 的 XML 文件,并添加到 StandardHost。这些 XML 文件称为应用程序描述符。正因为如此,我们可以配置一个虚拟路径来保存应用程序中用到的图片,详细的配置过程请参考 开发环境配置指南 – 6.3. 配置图片存放目录

  2. 部署 $CATALINA_BASE/webapps 下所有的 WAR 文件,并添加到 StandardHost。

  3. 部署 $CATALINA_BASE/webapps 下所有已解压的目录,并添加到 StandardHost。

特别的,添加到 StandardHost 时,会直接调用 StandardContext 的 start() 方法来启动应用程序。启动应用程序步骤请看 Context Start 一节。另外,MySQL 系列面试题和答案全部整理好了,微信搜索互联网架构师,在后台发送:2T,可以在线阅读。

  1. addListeners(engine) 方法会将该监听器添加到 StandardEngine 和它的所有子容器中

  2. registerHost() 会注册所有的 Host 和他们的子容器到 Mapper 中,方便后期请求处理时使用。

  3. 当有新的应用(StandardContext)添加进来后,会触发 Host 的容器事件,然后通过 MapperListener 将新应用的映射注册到 Mapper 中。

Context Start

7439c0d034ae1c0efeaf664755761517.png
  • StandRoot 类实现了 WebResourceRoot 接口,它容纳了一个应用程序的所有资源,通俗的来说就是部署到 webapps 目录下对应 Context 的目录里的所有资源。因为我对 Tomcat 的资源管理部分暂时不是很感兴趣,所以资源管理相关类只是做了简单了解,并没有深入研究源代码。

  • resourceStart() 方法会对 StandardRoot 进行初始配置

  • postWorkDirectory() 用于创建对应的工作目录 $CATALINA_BASE/work///, 该目录用于存放临时文件。

  • StardardContext 只是一个容器,而 ApplicationContext 则是一个应用程序真正的运行环境,相关类及操作会在请求处理流程看完以后进行补充。

  • StardardContext 触发 CONFIGURE_START_EVENT 生命周期事件,ContextConfig 开始调用 configureStart() 对应用程序进行配置。

  1. 这个过程会解析并合并 conf/web.xml & conf///web.xml.default & webapps//WEB-INF/web.xml 中的配置。

  2. 配置配置文件中的参数到 StandardContext, 其中主要的包括 Servlet、Filter、Listener。

  3. 因为从 Servlet3.0 以后是直接支持注解的,所以服务器必须能够处理加了注解的类。Tomcat 通过分析 WEB-INF/classes/ 中的 Class 文件和 WEB-INF/lib/ 下的 jar 包将扫描到的 Servlet、Filter、Listerner 注册到 StandardContext。

  4. setConfigured(true),是非常关键的一个操作,它标识了 Context 的成功配置,若未设置该值为 true 的话,Context 会启动失败。

Background process

452b960b2b15f0682bb4abd83a8dcbbb.png
  • 后台进程的作用就是处理一下 Servlet 引擎中的周期性事件,处理周期默认是 10s。

  • 特别的 StandardHost 的 backgroundProcess() 方法会触发 Host 的 PERIODIC_EVENT 生命周期事件。然后 HostConfig 会调用其 check() 方法对已加载并进行过重新部署的应用程序进行 reload 或对新部署的应用程序进行热部署。热部署跟之前介绍的部署步骤一致, reload() 过程只是简单的顺序调用 setPause(true)、stop()、start()、setPause(false),其中 setPause(true) 的作用是暂时停止接受请求。

How to read excellent open source projects

真正的第一次阅读开源项目源代码,收获还是很大的。让我在架构设计、面向对象思想、设计模式、Clean Code等等各个方面都有了进步。阅读优秀的开源项目其实是一件很爽的事,因为时不时的会发现一个新的设计思路,然后不由自主的感叹一声居然还可以这样!

当然了,读的时候还是会有一些痛点的,比如说碰到一个变量,但是死活就是找不到初始化的位置,有时通过 Find Usage 工具可以找到,但有些找不到的只能从头开始再过一边源码。有时碰到一个设计思路死活都想不明白为什么这样设计等等,这种情况就只能通过分析更高一层的架构来解决了等等。

下面我简单分享一下我是如何阅读开源项目源码的。

  • 先找一些介绍该项目架构的书籍来看,项目架构是项目核心中的核心,读架构读的是高层次的设计思路,读源码读的是低层次的实现细节。有了高层次的设计思路做指导,源码读起来才会得心应手,因为读的时候心里很清楚现在在读的源码在整个项目架构中处于什么位置。我在读 Tomcat 源码之前先把 《How Tomcat works》 一书过了一边,然后又看了一下 《Tomcat 架构解析》 的第二章,对 Tomcat 的架构有了初步了解。(PS:《How Tomcat works》一书是全英文的,但读起来非常流畅,虽然它是基于 Tomcat 4 和 5 的,但 Tomcat 架构没有非常大的变化,新版的 Tomcat 只是增加了一些组件,如果你要学习 Tomcat 的话,首推这本书!)

  • 如果实在找不到讲架构的书,那就自己动手画类图吧!一般来说,开源项目都是为了提供服务的,我们把提供服务的流程作为主线来分析源代码,这样目的性会更强一些,将该流程中涉及到的类画到类图中,最后得到的类图就是架构!不过分析之前你要先找到流程的入口点,否则分析就无从开始。以 Tomcat 为例,他的主线流程大致可以分为 3 个:启动、部署、请求处理。他们的入口点就是 Bootstrap 类和 接受请求的 Acceptor 类!

  • 有了阅读思路我们下面来说说工具吧。我使用的阅读工具是 IntelliJ IDEA,一款十分强大的 IDE,可能比较重量级,如果你有其他更加轻量级的 Linux 平台源码阅读工具,可以推荐给我~

  1. Structure 栏目可以自定义列出类中的域、方法,然后还可以按照继承结构对域和方法进行分组,这样就可以直接看出来域和方法是在继承结构中哪个类里定义的。当你点击方法和域时,还可以自动滚动到源代码等等。

    在源代码中 点击右键 -> Diagrams -> show Diagram 可以显示类的继承结构,图中包含了该类所有的祖先和所有的接口。在该图中选择指定的父类和接口,点击右键 -> show Implementations, IDEA 会列出接口的实现类或该类的子类。

    FindUsage、Go To Declaration 等等就不再多说了。

    Reference

https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X 
http://product.dangdang.com/25084132.html
https://tomcat.apache.org/tomcat-9.0-doc/index.html
http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

a7b8f8247b534b846b404592f0435888.png

点击👆卡片,关注后回复【面试题】即可获取

在看点这里f00b5e8586fddffdfc0201768d118436.gif好文分享给更多人↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值