刚接触 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.shWindows 中对应的是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 └── WrapperEngine:处理当前 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 自动配置完成。
参考资料
Apache Tomcat Architecture Overview
Apache Tomcat Startup Architecture
Apache Tomcat Configuration Reference