摘要:本文从使用底层API实现web容器说起,介绍了Servlet的本质,说明了Tomcat所要实现的功能。简要介绍了Tomcat的核心组件Connector、Container,以及http请求如何被处理。使读者对Tomcat核心内容有了整体认知。
本文中源码来自tomcat 9.0.x版本,地址github.com/apache/tomc…
一 最初的模样
1.1 简易Web容器
在 Tomcat 等成熟 Web 容器出现之前,开发者主要基于java.net 包(核心是 ServerSocket 和 Socket)手动实现简易 Web 服务器。 现在,我们使用底层API来实现一个Web Server,直观地感受 HTTP 协议和 Web 服务器的底层原理 。
首先,需要明确Web 服务器本质:
- 监听指定端口(如 8080),等待客户端(浏览器)连接;
- 接收客户端发送的 HTTP 请求,解析请求行 / 头 / 体;
- 根据请求路径返回对应的 HTTP 响应(静态资源或动态内容);
- 关闭 Socket 连接。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* 基于 java.net 实现的简易 Web 服务器,访问静态资源
*/
public class SimpleWebServer {
// 服务器监听端口
private static final int PORT = 8080;
// 静态资源根目录(如 "D:/webroot")
private static final String WEB_ROOT = System.getProperty("user.dir") + "/webroot";
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
while (true) {
// 阻塞等待客户端连接,获取 Socket 实例
Socket clientSocket = serverSocket.accept();
System.out.println("新客户端连接:" + clientSocket.getInetAddress());
// 启动独立线程处理
new Thread(new RequestHandler(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("服务器启动失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 处理单个客户端请求的线程类
*/
static class RequestHandler implements Runnable {
private final Socket clientSocket;
public RequestHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try (
BufferedReader reader = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8)
);
OutputStreamWriter writer = new OutputStreamWriter(
clientSocket.getOutputStream(), StandardCharsets.UTF_8
)
) {
// 1. 解析 HTTP 请求行(第一行格式:GET /index.html HTTP/1.1)
String[] requestParts = requestLine.split(" ");
String method = requestParts[0];
String requestPath = requestParts[1];
if ("GET".equalsIgnoreCase(method)) {
handleGetRequest(requestPath, writer);
} else {
// 仅支持 GET 方法,其他返回 405
sendResponse(writer, 405, "Method Not Allowed", "不支持的请求方法:" + method);
}
} catch (IOException e) {
System.err.println("处理客户端请求失败:" + e.getMessage());
} finally {
try {
// 关闭客户端连接(HTTP 1.0 短连接)
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理 GET 请求,返回静态资源或默认页面
*/
private void handleGetRequest(String requestPath, OutputStreamWriter writer) throws IOException {
// 读取服务器本地文件
sendResponse(writer, 200, "OK", content.toString());
}
/**
* 通用响应发送方法
*/
private void sendResponse(OutputStreamWriter writer, int statusCode, String statusMsg, String content) throws IOException {
// 构建 HTTP 响应头
writer.write("HTTP/1.1 " + statusCode + " " + statusMsg + "\r\n");
writer.write("Content-Type: text/html; charset=UTF-8\r\n");
writer.write("Content-Length: " + content.getBytes(StandardCharsets.UTF_8).length + "\r\n");
// 短连接
writer.write("Connection: close\r\n");
writer.write("\r\n");
// 写入响应体
writer.write(content);
writer.flush();
}
}
}
上面代码仅仅提供了访问静态资源这一基础功能,无法用于企业生产。而Tomcat等web容器提供了更强大的功能:
- 多线程优化(线程池、NIO 而非 BIO);
- HTTP 协议完整支持(POST/PUT/DELETE、Cookie/Session、文件上传);
- Servlet 规范实现(动态资源处理、请求分发);
- 安全控制、性能优化、集群部署等企业级特性。
其实,Tomcat 本质是对原生 java.net / java.nio 的封装,解决了原生开发的复杂性,实现了 Servlet 规范和企业级特性。
1.2 Servlet原生实现
在Tomcat等Web容器出现后,我们是如何做服务端开发的?这就需要用到javax.servlet 包,通过实现Servlet接口,提供访问动态资源能力。 而Servlet本质就是对 HTTP 请求的封装。
- 首先在maven项目的 pom.xml 中引入Servlet依赖。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
- 然后继承HttpServlet,实现业务逻辑(如用户查询)。
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 基于标准 javax.servlet.Servlet 实现的用户列表查询 Servlet
*/
@WebServlet("/user/list") // 映射路径:http://ip:port/user/list
public class UserListServlet extends HttpServlet {
/**
* 处理 GET 请求(核心方法)
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
PrintWriter writer = response.getWriter();
List<User> userList = queryUsers();
writer.write(JSON.toJSONString(userList));
writer.flush();
writer.close();
}
/**
* 模拟数据库查询
*/
public List<User> queryUsers() throws ServletException {
List<User> userList = new ArrayList<>();
userList.add(new User(1, "张三", 25));
userList.add(new User(2, "李四", 30));
userList.add(new User(3, "王五", 28));
return userList;
}
/**
* 销毁方法(Servlet 生命周期:服务器关闭时调用)
*/
@Override
public void destroy() {
super.destroy();
}
}
- 将Servlet交给Web容器管理,就能通过http://ip:8080/user/list来访问了。
二 Tomcat概览
Tomcat 其实主要做了两件事:
- HTTP 服务器:接收请求、解析协议、返回响应
- Servlet 容器:管理 Servlet、Filter、Listener、Session、类加载
2.1 核心组件
Tomcat有 4 层核心组件:
- Server:整个 Tomcat 实例,包含多个Service,并负责启动和管理它们;
- Service:一组 Connector + 一个 Engine
- Connector:接收网络请求(HTTP、AJP), 通过标准的 ServletRequest、ServletResponse与Container通信,输出响应;
- Container:容器层级,Engine → Host → Context → Wrapper
2.2 Connector组件
Connector是Tomcat 与客户端的连接器,监听固定端口接收外部请求,并将 Servlet的处理结果响应给外部。它有 3 个核心功能:
- 基于Socket实现网络通信;
- 应用层协议解析,如http;
- Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。
为此,Tomcat 的设计者设计了 3 个组件:EndPoint、Processor 和 Adapter。
- EndPoint:封装Socket,提供字节流给 Processor,接收响应写出字节流到Socket;
- Processor:处理应用层协议,负责提供 Tomcat Request 对象给 Adapter,响应 Tomcat Response给EndPoint ;
- Adapter:负责提供 ServletRequest 给容器,并接收ServletResponse。
由于 I/O 模型和应用层协议可以自由组合,Tomcat 将网络通信和应用层协议的实现放在一起,设计了ProtocolHandler 接口。 实现类如AjpNioProtocol、Http11NioProtocol等。
2.3 Container组件
Tomcat中设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。它们之间不是平行关系,而是父子关系。
- Engine:引擎,Servlet 的顶层容器,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine;
- Host:虚拟站点,负责 web 应用的部署和 Context 的创建;
- Context:Web 应用上下文,包含多个 Wrapper,负责 web 配置的解 析、管理所有的 Web 资源。一个Context对应一个 Web 应用程序。
- Wrapper:最底层的容器,是对 javax.servlet.Servlet的封装,负责 Servlet 实例的创建、执行和销毁。
同时提供了 Container 顶层接口,来实现层级管理、生命周期管理。
public interface Container extends Lifecycle {
// 部分方法
public void setName(String name);
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}
三 核心工作原理
我们已知晓Tomcat支持http协议,实现了Servlet规范。当客户端发送某个请求时,在Tomcat中又是如何被处理的?
3.1 定位servlet
当客户端或浏览器访问某个URL(如http://localhost:8080/myapp/user/login)时,如何定位到我们自定义的业务接口呢?
Tomcat中提供了Mapper接口,使用多级映射表,存储维护URL到容器的映射关系。按照 精确匹配 > 路径匹配 > 扩展名匹配 > 默认Servlet 的优先级,完成URL到Servlet的查找。
这块涉及3个组件:
- Mapper,映射器,维护URL到容器的映射关系
- MapperListener:监听器,动态更新Mapper中的映射关系
- CoyoteAdapter:适配器,连接Connector和Container,调用Mapper进行URL映射
核心简化代码如下
// 核心映射方法
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData) {
// 1. 查找Host
MappedHost[] hosts = this.hosts;
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
// 2. 查找Context
ContextList contextList = mappedHost.contextList;
MappedContext[] contexts = contextList.contexts;
// 3. 查找Wrapper
internalMapWrapper(contextVersion, uri, mappingData);
}
// org.apache.catalina.connector.CoyoteAdapter
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res) {
// 创建Request和Response对象
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
// 执行URL映射
connector.getService().getMapper().map(
serverName, decodedURI, version, request.getMappingData());
// 调用容器处理
connector.getService().getContainer().getPipeline()
.getFirst().invoke(request, response);
}
3.2 执行servlet
连接器中Adapter#service 方法,会获取Engine 容器处理请求,然后传给子容器 Host 继续处
理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用某个 Servlet 来执行业务逻辑。
整个过程使用了 Pipeline-Valve 管道,类似于 职责链模式。
- Pipeline接口是对管道的抽象,每个容器都关联一个 Pipeline 实例。一个Pipeline中有多个有序的Value对象,最后一个Value负责将请求传递给下一层Container。
public interface Pipeline extends Contained {
Container getContainer();
// 必须将请求传递给下一层容器
Valve getBasic();
void addValve(Valve valve);
void removeValve(Valve valve);
Valve[] getValves();
}
- Valve接口声明了处理请求的一个逻辑单元,就像管道上一个个阀门,比如权限认证、记录日志等。每层容器都有一个基础实现,如StandardEngineValve、StandardWrapperValve。
public interface Valve {
Valve getNext();
void invoke(Request request, Response response) throws IOException, ServletException;
}
用一张图表示整个过程
最终在StandardWrapperValve中,调用了Filter、Servlet。
那么,Valve 和 Filter 有什么区别:Valve 是 Tomcat 的私有机制,其他Web容器可没有;Filter是Servlet API 标准的接口,所有 Web 容器都必须支持。
3.3 生命周期管理
Tomcat通过 Lifecycle 接口,提供了一致的机制来启动和停止各个组件。
上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动。因此我们把组件的生命周期定义成一个个状态,把状态的转变看作是一个事件,通过监听器观察状态变化,触发相应的动作。这不就是观察者模式吗?
Lifecycle中提供了添加、移除LifecycleListener的方法。
void addLifecycleListener(LifecycleListener listener);
void removeLifecycleListener(LifecycleListener listener);
LifecycleListener仅有一个方法,实现监听事件LifecycleEvent。使用LifecycleState建立状态和事件的映射关系。
Tomcat 定义一个基类 LifeCycleBase 来实现 LifeCycle 接口,把一些公共的逻辑放到
基类中去,比如生命状态的转变与维护、生命周期事件的触发以及监听器的添加和删除等,
而子类就负责实现自己的初始化、启动和停止等方法。
LifeCycleBase 还定义了相应的抽象方法交给具体子类去实现。这儿使用了模板方法模式。