β

谈谈 Tomcat 架构及启动过程[含部署]

ImportNew 41 阅读
原文出处: Rainstorm

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

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

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

Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未发布,它实现了 Servlet4.0 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,详情请查阅 Tomcat-9.0-doc


Overview

  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

  1. 通过从 CatalinaProperties 类中获取 common.loader 等属性,获得类加载器的扫描仓库。 CatalinaProperties 类在的静态块中调用了 loadProperties() 方法,从 conf/catalina.properties 文件中加载了属性.(即在类创建的时候属性就已经加载好了)。
  2. 通过 ClassLoaderFactory 创建 URLClassLoader 的实例
  1. EngineConfig LifecycleListener 的实现类,触发 Engine 的生命周期事件后调用,这个监听器没有特别大的作用,就是打印一下日志
  2. HostConfig LifecycleListener 的实现类,触发 Host 的生命周期事件后调用。这个监听器的作用就是部署应用程序,这包括 conf/<Engine>/<Host>/ 目录下所有的 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]

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

特别的,添加到 StandardHost 时,会直接调用 StandardContext start() 方法来启动应用程序。启动应用程序步骤请看 Context Start 一节。

  1. addListeners(engine) 方法会将该监听器添加到 StandardEngine 和它的所有子容器中
  2. registerHost() 会注册所有的 Host 和他们的子容器到 Mapper 中,方便后期请求处理时使用。
  3. 当有新的应用( StandardContext )添加进来后,会触发 Host 的容器事件,然后通过 MapperListener 将新应用的映射注册到 Mapper 中。

Context Start

  1. 这个过程会解析并合并 conf/web.xml & conf/<Engine>/<Host>/web.xml.default & webapps/<Context>/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

How to read excellent open source projects

真正的第一次阅读开源项目源代码,收获还是很大的。让我在架构设计、面向对象思想、 设计模式 、Clean Code等等各个方面都有了进步。阅读优秀的开源项目其实是一件很爽的事,因为时不时的会发现一个新的设计思路,然后不由自主的感叹一声居然还可以这样!当然了,读的时候还是会有一些痛点的,比如说碰到一个变量,但是死活就是找不到初始化的位置,有时通过 Find Usage 工具可以找到,但有些找不到的只能从头开始再过一边源码。有时碰到一个设计思路死活都想不明白为什么这样设计等等,这种情况就只能通过分析更高一层的架构来解决了等等。

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

  1. Structure 栏目可以自定义列出类中的域、方法,然后还可以按照继承结构对域和方法进行分组,这样就可以直接看出来域和方法是在继承结构中哪个类里定义的。当你点击方法和域时,还可以自动滚动到源代码等等。
  2. 在源代码中 点击右键 -> Diagrams -> show Diagram 可以显示类的继承结构,图中包含了该类所有的祖先和所有的接口。在该图中选择指定的父类和接口, 点击右键 -> show Implementations , IDEA 会列出接口的实现类或该类的子类。
  3. FindUsage、Go To Declaration 等等就不再多说了。
  4. 目前想到的就这么多,如果你发现了我没有提到的功能,欢迎跟我邮件交流~

Reference

  1. 《How Tomcat works》
  2. 《Tomcat 架构解析》– 刘光瑞
  3. Tomcat-9.0-doc
  4. apache-tomcat-9.0.0.M22-src
作者:ImportNew
原文地址:谈谈 Tomcat 架构及启动过程[含部署], 感谢原作者分享。

发表评论