刚接触 Java Web 时,我一直把 Tomcat 简单理解成"运行 Java 项目的服务器"。后来接触 Nginx、Apache HTTP Server,又看到 Tomcat 自己也能监听 8080 端口,几个概念很容易混在一起。
其实只要分清"谁负责接收请求"和"谁负责执行 Servlet",再顺着 Tomcat 的启动过程看一遍,它的整体结构并没有想象中复杂。
一、Tomcat、Apache 和 Nginx 有什么区别
这里所说的 Apache,一般是指 Apache HTTP Server,而不是整个 Apache 软件基金会。
Nginx 和 Apache HTTP Server 主要定位为 Web 服务器,擅长处理静态资源、反向代理、负载均衡和 HTTPS。Tomcat 则首先是一个 Servlet 容器,主要用于部署和运行 Java Web 应用。
简单对比如下:
| 对比项 | Tomcat | Nginx | Apache HTTP Server |
|---|---|---|---|
| 核心定位 | Servlet 容器 | Web 服务器、反向代理 | Web 服务器 |
| Java Servlet/JSP | 原生支持 | 不直接支持 | 不直接支持 |
| 静态资源 | 可以处理,但不是主要优势 | 性能和并发能力较强 | 支持完善 |
| 反向代理 | 可以实现,但不常作为主要网关 | 非常常见 | 支持 |
| 常见场景 | 运行 Spring MVC、Servlet 应用 | 网关、负载均衡、静态资源 | 静态站点、模块化 Web 服务 |
Tomcat 本身也包含 HTTP Connector,所以它可以独立接收 HTTP 请求,并不是必须与 Nginx 搭配。开发环境中,我们直接访问 Spring Boot 的 8080 端口,本质上就是在访问内嵌 Tomcat。
生产环境经常采用下面的结构:
浏览器 → Nginx → 多个 Tomcat 实例 → Java 应用
这样做并不是因为 Tomcat 无法提供 HTTP 服务,而是希望让 Nginx 负责域名、HTTPS、静态资源、限流和负载均衡,Tomcat 专心运行 Java 业务。
二、Tomcat 的启动过程
我们在 Linux 中通常执行:
bin/startup.sh
Windows 中对应的是 startup.bat。脚本的主要作用是准备环境变量和 JVM 参数,随后调用 catalina.sh start,最终启动 Java 入口类:
org.apache.catalina.startup.Bootstrap.main(args)
Tomcat 的启动过程可以概括为下面几步。
1. 脚本准备运行环境
2. Bootstrap 创建类加载器
3. Catalina 解析 server.xml
下面是一段简化配置:
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" />
</Engine>
</Service>
</Server>
4. 初始化和启动各级组件
启动脚本 → Bootstrap → Catalina → 解析 server.xml
→ 创建组件 → 初始化组件 → 启动组件 → 监听请求
三、Tomcat 的整体分层架构
Tomcat 的核心架构中,最常见的四个名称是 Server、Service、Connector 和 Container。
Server:代表整个 Tomcat
Server 是 server.xml 的最外层元素,代表整个 Catalina Servlet 容器。一个 Tomcat 实例通常只有一个 Server,其中可以包含多个 Service。
Service:连接网络层和容器层
Service 不是直接处理业务的组件,它更像一个组合层:一个 Service 可以包含多个 Connector,但通常只关联一个 Engine。
例如 HTTP 端口 8080 和 HTTPS 端口 8443 可以使用两个 Connector,最后把请求交给同一个 Engine,这样两种入口可以共享同一批 Web 应用。
Connector:负责网络和协议
Connector 负责监听端口、接收连接、解析 HTTP 或 AJP 协议,并把网络请求转换成 Tomcat 内部能够处理的 Request。业务处理完成后,它还负责把 Response 写回客户端。
Connector 解决的是"请求怎么进来、响应怎么出去",并不负责判断最终调用哪个 Servlet。
Container:负责找到并执行 Servlet
Container 负责请求在 Servlet 容器内部的处理。它还可以继续分为四层:
Engine
└── Host
└── Context
└── Wrapper
-
Engine:处理当前 Service 中所有 Connector 转交的请求; -
Host:代表一个虚拟主机,例如www.example.com; -
Context:代表一个 Web 应用,例如/shop; -
Wrapper:代表一个具体 Servlet,例如 Spring MVC 的DispatcherServlet。
假设访问 http://localhost:8080/shop/order/list,Connector 先接收并解析请求,Engine 再找到 localhost 对应的 Host,Host 根据 /shop 找到 Context,最后由 Wrapper 调用对应的 Servlet。
可以把整体关系简单记为:Server 管整个 Tomcat,Service 组合入口与容器,Connector 处理网络,Container 执行业务请求。
四、load-on-startup 有什么作用
在传统 Java Web 项目的 web.xml 中,可以通过 <load-on-startup> 控制 Servlet 的创建时机:
<servlet>
<servlet-name>initServlet</servlet-name>
<servlet-class>com.example.InitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
如果配置的是非负整数,Tomcat 会在 Web 应用启动阶段创建 Servlet,并调用它的 init() 方法,而不是等第一次请求到来时才初始化。
多个 Servlet 都配置了 load-on-startup 时,数值较小的会优先加载。例如,值为 1 的 Servlet 通常早于值为 5 的 Servlet;相同数值之间的先后顺序不应依赖。如果配置负数或者没有配置,一般会在第一次请求匹配到该 Servlet 时再创建。
使用注解也能达到同样效果:
@WebServlet(urlPatterns = "/init", loadOnStartup = 1)
public class InitServlet extends HttpServlet {
@Override
public void init() {
System.out.println("应用启动时完成初始化");
}
}
它常用于提前初始化必要资源,让第一次请求不用承担初始化耗时。不过,不建议把大量数据加载、长时间远程调用或者不稳定任务全部放进 init()。一旦初始化抛出异常,Servlet 可能不可用,甚至影响应用正常启动。
在 Spring Boot 项目中,我们很少直接配置 Servlet 的 load-on-startup,但它并没有消失。Spring MVC 的 DispatcherServlet 同样由 Servlet 容器管理,只是大部分注册工作由 Spring Boot 自动配置完成。