从Bootstrap到Connector: Tomcat启动全过程

112 阅读9分钟

Tomcat的启动大致可以分为以下几个步骤:

  1. 启动脚本解析和JVM初始化
  1. 创建Catalina实例
  • Bootstrap的main()方法会创建Catalina实例,并调用其process()方法。
  • process()方法会解析命令行参数,如"start"、"stop"等,并调用对应的方法。
  1. 加载配置文件
  • Catalina的load()方法被调用,开始加载配置文件。
  • 加载catalina.properties文件,解析其中的配置参数。
  • 加载server.xml文件,解析Server、Service、Connector、Engine、Host、Context等组件的配置。
  1. 初始化Server组件
  • 根据server.xml中的配置,创建Server实例,调用其init()方法完成初始化。
  • Server实例会创建并初始化Service组件。
  1. 初始化Service组件
  • Service组件会创建并初始化其包含的Connector和Engine组件。
  • Connector组件负责接收客户端请求,Engine组件负责处理请求。
  1. 初始化Engine、Host、Context组件
  • Engine组件会创建并初始化其包含的Host组件。
  • Host组件表示一个虚拟主机,会创建并初始化其包含的Context组件。
  • Context组件表示一个Web应用,会加载应用的部署描述符web.xml,并创建和初始化Servlet、Filter、Listener等组件。
  1. 启动Connector组件
  • Catalina的start()方法被调用,开始启动Server中的Service。
  • Service会启动其包含的Connector组件,开始监听配置的端口,等待请求到来。
  1. 请求处理
  • 当请求到来时,Connector组件会创建Request和Response对象,并将请求交给Engine组件处理。
  • Engine组件会根据请求的Host和Context信息,将请求交给对应的Host和Context处理。
  • Context会根据请求的URL,匹配对应的Servlet进行处理,并将处理结果写入Response。
  • Connector从Response中获取处理结果,生成HTTP响应返回给客户端。

以上就是Tomcat启动的详细流程。主要涉及的类和方法有:

  • Bootstrap: main()
  • Catalina: process()、load()、start()
  • Server: init()、start()
  • Service: init()、start()
  • Connector: init()、start()
  • Engine: init()、start()
  • Host: init()、start()
  • Context: init()、start()

这些类都实现了Lifecycle接口,具有init()、start()、stop()、destroy()等生命周期方法,在Tomcat启动和停止过程中被调用。

graph TD
    A[启动脚本解析和JVM初始化] --> B[运行startup.sh/startup.bat脚本,解析脚本中的配置参数]
    A --> C[根据脚本配置初始化JVM]
    A --> D[执行Bootstrap类的main方法]
    
    D --> E[创建Catalina实例]
    E --> F[调用Catalina的process方法]
    F --> G[解析命令行参数,并调用对应的方法]
    
    G --> H[加载配置文件]
    H --> I[调用Catalina的load方法]
    I --> J[加载catalina.properties文件]
    I --> K[加载server.xml文件]
    
    K --> L[初始化Server组件]
    L --> M[根据server.xml中的配置,创建Server实例]
    M --> N[调用Server的init方法完成初始化]
    N --> O[Server实例创建并初始化Service组件]
    
    O --> P[初始化Service组件]
    P --> Q[Service组件创建并初始化Connector和Engine组件]
    
    Q --> R[初始化Engine/Host/Context组件]
    R --> S[Engine组件创建并初始化Host组件]
    S --> T[Host组件创建并初始化Context组件]
    T --> U[Context组件加载web.xml,创建和初始化Servlet/Filter/Listener等组件]
    
    U --> V[启动Connector组件]
    V --> W[调用Catalina的start方法]
    W --> X[Service启动其包含的Connector组件]
    
    X --> Y[请求处理]
    Y --> Z[Connector组件创建Request和Response对象,并将请求交给Engine组件处理]
    Z --> AA[Engine组件根据请求的Host和Context信息,将请求交给对应的Host和Context处理]
    AA --> AB[Context根据请求的URL,匹配对应的Servlet进行处理,并将处理结果写入Response]
    AB --> AC[Connector从Response中获取处理结果,生成HTTP响应返回给客户端]

试着用一个简单的类比: 你可以把Tomcat看做一个餐馆,Catalina就是餐馆经理,它负责餐馆的整个运作流程。当顾客(请求)来到时,服务员(Connector)会接待顾客,然后把顾客交给厨师(Servlet)来准备食物(响应)。


JVM是如何执行Bootstrap类的main()方法?

  1. JVM启动
  • 当运行java命令时,操作系统会创建一个新的进程,并为该进程分配内存。
  • 操作系统会加载JVM的可执行文件(如java.exe或libjvm.so)到进程的内存中。
  • JVM会初始化,并为自己分配内存,创建堆、栈等内存区域。
  1. 加载Bootstrap类
  • JVM内置了一个启动类加载器(Bootstrap Class Loader),它负责加载JVM自身的核心类库。
  • Bootstrap类就位于rt.jar等核心类库中,启动类加载器会在JVM启动时加载这些类库。
  • 类加载器会将Bootstrap类的字节码从rt.jar中读取到内存中。
  1. 链接Bootstrap类
  • JVM会对加载到内存中的Bootstrap类进行链接(Link)。
  • 链接包括验证(Verify)、准备(Prepare)和解析(Resolve)三个步骤。
  • 验证步骤会检查类的字节码是否符合JVM规范。
  • 准备步骤会为类的静态字段分配内存,并初始化为默认值。
  • 解析步骤会将类中的符号引用转换为直接引用。
  1. 初始化Bootstrap类
  • JVM会对链接后的Bootstrap类进行初始化(Initialize)。
  • 初始化步骤会执行类的静态初始化器和静态初始化块。
  1. 调用Bootstrap.main()方法
  • 当Bootstrap类初始化完成后,JVM会创建一个新的线程,称为主线程(Main Thread)。
  • JVM会在主线程中调用Bootstrap类的main()方法。
  • Bootstrap.main()方法接收到命令行参数,并开始执行Tomcat的启动逻辑。
  1. 执行Bootstrap.main()方法
  • Bootstrap.main()方法会创建一个Catalina实例,并调用其process()方法。
  • process()方法会解析命令行参数,如"start"、"stop"等,并调用对应的方法。
  • 接下来的Tomcat启动流程就如之前所述。

总的来说,JVM执行Bootstrap.main()方法的过程可以概括为:

  1. JVM启动,创建进程并分配内存。
  2. 启动类加载器加载Bootstrap类到内存中。
  3. JVM对Bootstrap类进行链接,包括验证、准备和解析。
  4. JVM对Bootstrap类进行初始化,执行静态初始化器和静态初始化块。
  5. JVM创建主线程,并在主线程中调用Bootstrap.main()方法。
  6. Bootstrap.main()方法开始执行Tomcat的启动逻辑。
graph TD
    A[JVM启动] -->|创建进程并分配内存| B[加载Bootstrap类]
    B -->|启动类加载器加载Bootstrap类到内存中| C[链接Bootstrap类]
    C -->|验证准备和解析| D[初始化Bootstrap类]
    D -->|执行静态初始化器和静态初始化块| E[调用Bootstrap.main方法]
    E -->|创建主线程并调用main方法| F[执行Bootstrap.main方法]
    F -->|创建Catalina实例并调用process方法| G[Tomcat启动]

    classDef start fill:#f9f,stroke:#333,stroke-width:2px;
    classDef load fill:#bbf,stroke:#333,stroke-width:2px;
    classDef link fill:#bfb,stroke:#333,stroke-width:2px;
    classDef init fill:#ffb,stroke:#333,stroke-width:2px;
    classDef mainCall fill:#ff9,stroke:#333,stroke-width:2px;
    classDef mainExec fill:#9f9,stroke:#333,stroke-width:2px;
    classDef tomcat fill:#f99,stroke:#333,stroke-width:2px;
    
    class A start;
    class B load;
    class C link;
    class D init;
    class E mainCall;
    class F mainExec;
    class G tomcat;

创建Catalina实例之后做了什么?

1. 创建 Catalina 实例

Bootstrap 类的 main() 方法被执行时,首先会创建一个 Catalina 的实例。Catalina 类是Tomcat的核心类之一,它负责Tomcat的整个生命周期管理。

2. 设置 Catalina 实例属性

创建 Catalina 实例后,Bootstrap 会根据需要设置一些关键属性。这包括从命令行参数解析Tomcat的配置选项,如指定的配置文件路径等。

调用 Catalina 的 load() 方法做了啥?

随后,Bootstrap 会调用 Catalina 实例的 load() 方法。这个方法负责加载和解析Tomcat的配置文件(如 server.xml),并根据这些配置来设置Tomcat的内部组件,例如 ServiceConnectorEngineHostContext。这一步是准备Tomcat运行的基础设施和环境的关键阶段。

1. 识别配置文件

首先,Catalina实例需要确定要加载的配置文件。在启动时,可以通过命令行参数指定配置文件的位置,如果没有指定,则默认使用conf/server.xml

2. 解析配置文件

使用XML解析器(如Apache Xerces)来解析配置文件。这个过程包括读取XML文件中的元素、属性和文本内容,并构建一个对应的内存数据结构(通常是DOM树)。

3. 处理配置元素

Tomcat的配置文件server.xml通常会包含多个配置组件,如Server, Service, Connector, Engine, Host, Context等。每个元素代表Tomcat架构中的一个组件或容器,它们层次化地组成了Tomcat服务器的整体结构。解析过程中,Tomcat会按照这些元素的层次和顺序创建对应的Java对象,并设置对象的属性。

当解析到server.xml配置文件中的<Server>标签时,Tomcat创建一个对应的Server对象。这个对象将作为其他组件(如ServiceConnectorEngine等)的容器。

Server标签中可能包含一些属性,如port(用于监听关闭命令的端口)、shutdown(用于定义关闭命令的字符串)等。这些属性会被读取并设置到Server对象中。例如:

<Server port="8005" shutdown="SHUTDOWN">

这表示Server将监听端口8005上的关闭命令,如果接收到文本"SHUTDOWN",则会触发服务器的关闭过程。

Server组件还可能配置一些全局资源,如JNDI资源、全局Naming资源等。这些资源在Server层面上定义,并且可以被服务器上的所有应用共享。这可能涉及到解析<GlobalNamingResources>元素,并创建相应的资源实例。

Server组件包含一个或多个Service组件。每个Service是一个独立的运行实体,包括一个或多个Connector和一个Engine。在解析Server组件时,会对每个内部的Service元素进行解析和初始化。这包括:

  • 创建Service对象。
  • 设置Service的属性和关联的Server
  • 递归地初始化每个Service内部的ConnectorEngine组件。

示例:

  • <Server>:最顶层的元素,代表整个Tomcat服务器。
  • <Service>:属于Server,代表一个服务实例,可以包含多个Connector和一个Engine
  • <Connector>:属于Service,负责处理特定类型的网络连接,如HTTP、AJP等。
  • <Engine>:也属于Service,是请求处理的核心,可以包含多个Host
  • <Host>:属于Engine,代表一个虚拟主机。
  • <Context>:属于Host,表示一个Web应用程序。

4. 配置对象的初始化

每个Java对象在创建时,会根据配置文件中的参数进行初始化。例如,Connector对象会被配置端口号、协议类型等信息。这些对象会保存相关的配置,以便在Tomcat运行时按照这些配置进行操作。

5. 建立组件关系

在加载和解析过程中,Tomcat不仅创建了各个组件的实例,还需要正确地设置它们之间的关系。例如,确保每个Service知道它包含哪些Connector和一个Engine,每个Engine知道它包含哪些Host,以及每个Host知道它管理哪些Context

6. 准备就绪

在所有配置文件被正确解析并且所有组件对象被创建和配置之后,Tomcat准备就绪,等待start()方法的调用来启动服务。

graph TD
    A[开始] --> B[加载配置文件 server.xml]
    B --> C[创建Server实例]
    C --> D[配置Server属性]
    D --> E[初始化全局资源]
    E --> F{遍历<Service>元素}
    F -->|存在| G[创建Service实例]
    G --> H[配置Service属性]
    H --> I{遍历<Connector>元素}
    I -->|存在| J[创建Connector实例]
    J --> K[配置Connector属性]
    K --> I
    I -->|不存在| L{遍历<Engine>元素}
    L -->|存在| M[创建Engine实例]
    M --> N[配置Engine属性]
    N --> O{遍历<Host>元素}
    O -->|存在| P[创建Host实例]
    P --> Q[配置Host属性]
    Q --> R{遍历<Context>元素}
    R -->|存在| S[创建Context实例]
    S --> T[配置Context属性]
    T --> R
    R -->|不存在| O
    O -->|不存在| L
    L -->|不存在| F
    F -->|不存在| U[监听关闭命令]
    U --> V[服务器准备就绪]
    V --> W[结束]

调用 Catalina 的 start() 方法启动了啥?

加载完成后,Bootstrap 会调用 Catalina 实例的 start() 方法。这个方法负责启动Tomcat服务器。在这一步,Tomcat会启动各种服务组件,包括:

  • 启动 Connector,它负责接收来自客户端的网络连接和请求。
  • 启动 Engine,它是Tomcat的核心容器,负责处理请求的生命周期。
  • 启动所有配置的 Host 和 Context,这些代表了虚拟主机和Web应用程序。

每个组件在启动时都会初始化自己的资源,并准备好处理来自客户端的请求。

等待关闭命令

在成功启动所有组件后,Catalinastart() 方法会进入阻塞状态,等待接收到停止命令。当接收到停止命令时(例如通过调用 Catalinastop() 方法),Tomcat开始关闭过程。

调用 Catalina 的 stop() 方法干了啥?

当Tomcat接收到停止命令时,Catalinastop() 方法会被调用。这个方法负责优雅地关闭所有组件,释放资源,并确保所有正在处理的请求都被正确完成。

graph TD
    A[创建 Catalina 实例] --> B[设置 Catalina 实例属性]
    B --> C[调用 Catalina 的 load 方法]
    C --> D[调用 Catalina 的 start 方法]
    D --> E[等待关闭命令]
    E --> F[调用 Catalina 的 stop 方法]
    F --> G[Tomcat 服务器关闭]

    classDef start fill:#f9f,stroke:#333,stroke-width:2px;
    classDef create fill:#bbf,stroke:#333,stroke-width:2px;
    classDef config fill:#bfb,stroke:#333,stroke-width:2px;
    classDef load fill:#ffb,stroke:#333,stroke-width:2px;
    classDef startServer fill:#ff9,stroke:#333,stroke-width:2px;
    classDef wait fill:#9f9,stroke:#333,stroke-width:2px;
    classDef stop fill:#f99,stroke:#333,stroke-width:2px;
    
    class A start;
    class B create;
    class C config;
    class D load;
    class E startServer;
    class F wait;
    class G stop;