Tomcat的启动大致可以分为以下几个步骤:
- 启动脚本解析和JVM初始化
- 运行startup.sh/startup.bat脚本,解析脚本中的配置参数,如CATALINA_HOME、CATALINA_BASE等。
- 根据脚本配置初始化JVM,如内存配置、类路径等。
- 执行Bootstrap类的main()方法。
- 创建Catalina实例
- Bootstrap的main()方法会创建Catalina实例,并调用其process()方法。
- process()方法会解析命令行参数,如"start"、"stop"等,并调用对应的方法。
- 加载配置文件
- Catalina的load()方法被调用,开始加载配置文件。
- 加载catalina.properties文件,解析其中的配置参数。
- 加载server.xml文件,解析Server、Service、Connector、Engine、Host、Context等组件的配置。
- 初始化Server组件
- 根据server.xml中的配置,创建Server实例,调用其init()方法完成初始化。
- Server实例会创建并初始化Service组件。
- 初始化Service组件
- Service组件会创建并初始化其包含的Connector和Engine组件。
- Connector组件负责接收客户端请求,Engine组件负责处理请求。
- 初始化Engine、Host、Context组件
- Engine组件会创建并初始化其包含的Host组件。
- Host组件表示一个虚拟主机,会创建并初始化其包含的Context组件。
- Context组件表示一个Web应用,会加载应用的部署描述符web.xml,并创建和初始化Servlet、Filter、Listener等组件。
- 启动Connector组件
- Catalina的start()方法被调用,开始启动Server中的Service。
- Service会启动其包含的Connector组件,开始监听配置的端口,等待请求到来。
- 请求处理
- 当请求到来时,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()方法?
- JVM启动
- 当运行java命令时,操作系统会创建一个新的进程,并为该进程分配内存。
- 操作系统会加载JVM的可执行文件(如java.exe或libjvm.so)到进程的内存中。
- JVM会初始化,并为自己分配内存,创建堆、栈等内存区域。
- 加载Bootstrap类
- JVM内置了一个启动类加载器(Bootstrap Class Loader),它负责加载JVM自身的核心类库。
- Bootstrap类就位于rt.jar等核心类库中,启动类加载器会在JVM启动时加载这些类库。
- 类加载器会将Bootstrap类的字节码从rt.jar中读取到内存中。
- 链接Bootstrap类
- JVM会对加载到内存中的Bootstrap类进行链接(Link)。
- 链接包括验证(Verify)、准备(Prepare)和解析(Resolve)三个步骤。
- 验证步骤会检查类的字节码是否符合JVM规范。
- 准备步骤会为类的静态字段分配内存,并初始化为默认值。
- 解析步骤会将类中的符号引用转换为直接引用。
- 初始化Bootstrap类
- JVM会对链接后的Bootstrap类进行初始化(Initialize)。
- 初始化步骤会执行类的静态初始化器和静态初始化块。
- 调用Bootstrap.main()方法
- 当Bootstrap类初始化完成后,JVM会创建一个新的线程,称为主线程(Main Thread)。
- JVM会在主线程中调用Bootstrap类的main()方法。
- Bootstrap.main()方法接收到命令行参数,并开始执行Tomcat的启动逻辑。
- 执行Bootstrap.main()方法
- Bootstrap.main()方法会创建一个Catalina实例,并调用其process()方法。
- process()方法会解析命令行参数,如"start"、"stop"等,并调用对应的方法。
- 接下来的Tomcat启动流程就如之前所述。
总的来说,JVM执行Bootstrap.main()方法的过程可以概括为:
- JVM启动,创建进程并分配内存。
- 启动类加载器加载Bootstrap类到内存中。
- JVM对Bootstrap类进行链接,包括验证、准备和解析。
- JVM对Bootstrap类进行初始化,执行静态初始化器和静态初始化块。
- JVM创建主线程,并在主线程中调用Bootstrap.main()方法。
- 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的内部组件,例如 Service、Connector、Engine、Host 和 Context。这一步是准备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对象。这个对象将作为其他组件(如Service,Connector,Engine等)的容器。
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内部的Connector和Engine组件。
示例:
<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应用程序。
每个组件在启动时都会初始化自己的资源,并准备好处理来自客户端的请求。
等待关闭命令
在成功启动所有组件后,Catalina 的 start() 方法会进入阻塞状态,等待接收到停止命令。当接收到停止命令时(例如通过调用 Catalina 的 stop() 方法),Tomcat开始关闭过程。
调用 Catalina 的 stop() 方法干了啥?
当Tomcat接收到停止命令时,Catalina 的 stop() 方法会被调用。这个方法负责优雅地关闭所有组件,释放资源,并确保所有正在处理的请求都被正确完成。
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;