标题

手写简易 Tomcat,理解 Servlet 容器核心原理

摘要

Tomcat 是 Java Web 开发的核心容器,但其底层原理常让开发者感到抽象。本文通过手写一个简易版 Tomcat,拆解 Servlet 容器的核心逻辑(Socket 通信、Servlet 加载、请求分发),帮助读者从底层理解 Tomcat 的工作机制。

正文
一、Tomcat 核心原理前置知识

Tomcat 本质是一个基于 Socket 的 HTTP 服务器,核心职责:

  1. 监听指定端口,接收客户端 HTTP 请求;
  2. 解析 HTTP 请求,匹配对应的 Servlet;
  3. 调用 Servlet 的 service 方法处理请求;
  4. 封装 HTTP 响应,返回给客户端。

Servlet 容器的核心:一个 HashMap,key 为 Servlet 访问路径(如 /hello),value 为 Servlet 实例。

二、核心步骤 1:定义 Servlet 接口(模拟标准 Servlet)

标准 Servlet 接口包含 init ()、service ()、destroy () 三个核心方法,我们先定义简化版:

java

运行

/**
 * 模拟Servlet接口
 */
public interface MyServlet {
    // 初始化
    void init();
    // 处理请求
    void service(MyRequest request, MyResponse response);
    // 销毁
    void destroy();
}
三、核心步骤 2:封装请求和响应对象

模拟 HTTP 请求和响应,简化版只需解析请求路径、封装响应内容:

java

运行

/**
 * 模拟请求对象
 */
public class MyRequest {
    private String method; // 请求方法GET/POST
    private String path; // 请求路径
    
    public MyRequest(InputStream inputStream) throws IOException {
        // 读取请求数据
        String content = "";
        byte[] buffer = new byte[1024];
        int len = inputStream.read(buffer);
        if (len > 0) {
            content = new String(buffer, 0, len);
        }
        // 解析请求行(如GET /hello HTTP/1.1)
        String line = content.split("\n")[0];
        String[] arr = line.split(" ");
        this.method = arr[0];
        this.path = arr[1];
    }
    
    public String getMethod() {
        return method;
    }
    
    public String getPath() {
        return path;
    }
}

/**
 * 模拟响应对象
 */
public class MyResponse {
    private OutputStream outputStream;
    
    public MyResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    
    // 封装HTTP响应
    public void write(String content) throws IOException {
        StringBuilder sb = new StringBuilder();
        sb.append("HTTP/1.1 200 OK\n")
          .append("Content-Type: text/html;charset=UTF-8\n")
          .append("\n")
          .append(content);
        outputStream.write(sb.toString().getBytes());
    }
}
四、核心步骤 3:实现自定义 Servlet

编写一个示例 Servlet,处理 /hello 路径的请求:

java

运行

public class HelloServlet implements MyServlet {
    @Override
    public void init() {
        // 初始化逻辑(如加载配置、连接数据库)
        System.out.println("HelloServlet初始化");
    }
    
    @Override
    public void service(MyRequest request, MyResponse response) throws IOException {
        // 处理请求并返回响应
        response.write("<h1>Hello, 简易Tomcat!</h1>");
    }
    
    @Override
    public void destroy() {
        // 销毁逻辑(如释放资源)
        System.out.println("HelloServlet销毁");
    }
}
五、核心步骤 4:实现 Servlet 容器(核心)

Servlet 容器的核心是维护 “路径 - Servlet” 映射关系,提供两种加载方式:

方式 1:启动时加载(Tomcat 启动慢,首次请求快)

java

运行

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * 简易Tomcat核心类
 */
public class MyTomcat {
    // 端口号
    private int port = 8080;
    // Servlet容器(路径-Servlet映射)
    private Map<String, MyServlet> servletMap = new HashMap<>();
    
    // 静态代码块:Tomcat启动时加载Servlet
    static {
        // 注册Servlet
        servletMap.put("/hello", new HelloServlet());
        // 初始化所有Servlet
        for (MyServlet servlet : servletMap.values()) {
            servlet.init();
        }
    }
    
    // 启动Tomcat
    public void start() throws IOException {
        // 监听8080端口
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("简易Tomcat启动,监听端口:" + port);
        
        // 循环接收客户端请求
        while (true) {
            Socket socket = serverSocket.accept();
            // 处理请求(简化版:单线程,实际Tomcat是多线程)
            processRequest(socket);
        }
    }
    
    // 处理请求
    private void processRequest(Socket socket) throws IOException {
        MyRequest request = new MyRequest(socket.getInputStream());
        MyResponse response = new MyResponse(socket.getOutputStream());
        
        // 匹配Servlet
        String path = request.getPath();
        if (servletMap.containsKey(path)) {
            servletMap.get(path).service(request, response);
        } else {
            response.write("<h1>404 Not Found</h1>");
        }
        
        // 关闭资源
        socket.close();
    }
    
    public static void main(String[] args) throws IOException {
        new MyTomcat().start();
    }
}
方式 2:首次请求时加载(Tomcat 启动快,首次请求慢)

只需修改 Servlet 加载逻辑,移除静态代码块,在 processRequest 中加载:

java

运行

private void processRequest(Socket socket) throws IOException {
    MyRequest request = new MyRequest(socket.getInputStream());
    MyResponse response = new MyResponse(socket.getOutputStream());
    
    String path = request.getPath();
    // 首次请求时加载Servlet
    if (!servletMap.containsKey(path)) {
        if ("/hello".equals(path)) {
            HelloServlet servlet = new HelloServlet();
            servlet.init();
            servletMap.put(path, servlet);
        } else {
            response.write("<h1>404 Not Found</h1>");
            socket.close();
            return;
        }
    }
    
    servletMap.get(path).service(request, response);
    socket.close();
}
六、运行测试
  1. 运行 MyTomcat 类,控制台输出 “简易 Tomcat 启动,监听端口:8080”;
  2. 打开浏览器,访问http://localhost:8080/hello;
  3. 页面显示 “Hello, 简易 Tomcat!”,控制台输出 “HelloServlet 初始化”。
七、核心原理总结
  1. Socket 通信:Tomcat 通过 ServerSocket 监听端口,接收客户端 TCP 连接;
  2. Servlet 容器:本质是 HashMap,维护 “访问路径 - Servlet 实例” 的映射;
  3. Servlet 生命周期:init ()(初始化)→ service ()(处理请求)→ destroy ()(销毁);
  4. 加载策略:启动时加载(静态代码块)或首次请求加载,各有优劣。
八、与真实 Tomcat 的差异

本文实现的是极简版 Tomcat,真实 Tomcat 还包含:

  • 多线程处理(避免单线程阻塞);
  • HTTP 协议完整解析(请求头、请求体、Cookie、Session);
  • Servlet 生命周期管理(由容器统一控制 init/destroy);
  • 连接器(Connector)、引擎(Engine)、主机(Host)、上下文(Context)等组件;
  • 安全校验、异常处理、日志记录等功能。

通过手写简易 Tomcat,我们剥离了复杂的外围组件,聚焦核心逻辑,能更清晰地理解 Tomcat 作为 Servlet 容器的本质 —— 本质是一个处理 HTTP 请求的 Socket 服务器,核心是 Servlet 的加载与请求分发。

更多推荐