在 Java Web 技术栈中,Servlet 是处理 HTTP 请求的核心组件,而Tomcat 是运行 Servlet 的容器—— 二者的协作是 Web 应用运行的基础。本文将结合底层流程图,从 Servlet 的体系结构、Tomcat 的请求处理流程,到自定义 Tomcat 的实现,完整拆解其底层逻辑。

一、Servlet:HTTP 请求处理的规范与实现

Servlet 并非独立的 “类”,而是一套请求处理的接口规范,其设计采用 “接口 - 抽象类 - 业务实现” 的分层模式,降低了开发复杂度。

1. Servlet 接口:定义核心生命周期

Servlet是所有 Servlet 组件的根接口,它规定了组件从创建到销毁的完整生命周期方法:

public interface Servlet {
    // 初始化:容器创建Servlet实例后调用,仅执行1次
    void init(ServletConfig config) throws ServletException;
    // 处理请求:每次客户端请求时触发,封装业务逻辑
    void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    // 获取组件信息:返回Servlet的描述性内容
    String getServletInfo();
    // 销毁:容器回收实例前调用,用于释放资源
    void destroy();
}

2. GenericServlet:简化接口实现

GenericServlet是一个抽象类,它实现了Servlet接口中除service()外的所有方法,核心作用是封装通用逻辑

  • 自动维护ServletConfig对象,提供getInitParameter()等工具方法;
  • 统一处理初始化与资源释放的重复代码。

开发者只需重写service()即可实现请求处理,但该类未适配 HTTP 协议,实际开发中较少直接使用。

3. HttpServlet:适配 HTTP 协议的实现

HttpServlet继承自GenericServlet,其核心是重写service()方法,并根据 HTTP 请求的方法类型(GET/POST/PUT 等),将请求分发到对应的doXxx()方法:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        this.doGet(req, resp);
    } else if (method.equals(METHOD_POST)) {
        this.doPost(req, resp);
    }
    // 其他HTTP方法的分发逻辑
}

4. 自定义 Servlet:业务逻辑落地

实际开发中,我们只需继承HttpServlet,重写对应请求方法即可实现业务逻辑,示例如下:

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("登录成功,用户名:" + username);
    }
}

二、Tomcat:Servlet 的运行容器与请求处理流程

Servlet 本身是普通 Java 类,必须依赖 Tomcat 这样的Servlet 容器才能运行。Tomcat 的核心职责是管理 Servlet 生命周期、接收请求、分发处理并返回响应,其完整流程如下:

1. 监听与接收请求

Tomcat 通过ServerSocket监听指定端口(默认 8080),由Acceptor线程接收客户端连接,再通过线程池分配工作线程处理请求(避免频繁创建线程的开销)。

2. 解析请求报文

Tomcat 将原始 HTTP 请求报文解析为HttpServletRequest对象,包含三部分核心内容:

  • 请求行:请求方法(GET/POST)、URI、HTTP 版本;
  • 请求头:Cookie、Content-Type 等首部字段;
  • 请求体:POST 请求的表单数据、JSON 等内容。

3. 匹配 Servlet 组件

Tomcat 根据请求 URI 查询Servlet 映射表(由web.xml@WebServlet注解配置),找到对应的 Servlet 类。若该 Servlet 实例未初始化,则调用init()方法完成初始化(仅执行一次)。

4. 调用 Servlet 处理请求

Tomcat 将HttpServletRequestHttpServletResponse对象传入 Servlet 的service()方法,通过HttpServlet的方法分发逻辑,最终执行自定义 Servlet 中重写的doGet()/doPost()方法。

5. 生成并返回响应

自定义 Servlet 通过HttpServletResponse对象封装响应数据(响应行、响应头、响应体),Tomcat 将其转换为 HTTP 响应报文,通过 Socket 写回客户端,完成请求处理。

三、自定义 Tomcat:手动实现简易容器

基于 Tomcat 的核心逻辑,我们可以实现一个简易的 Servlet 容器,核心步骤如下:

1. 配置 Servlet 映射

定义 URI 与 Servlet 类的映射关系,模拟 Tomcat 的映射表:

private static final Map<String, Class<? extends HttpServlet>> SERVLET_MAP = new HashMap<>();
static {
    SERVLET_MAP.put("/login", LoginServlet.class);
}

2. 监听端口并接收请求

创建ServerSocket监听端口,通过多线程处理客户端连接:

public static void start(int port) throws IOException {
    ServerSocket serverSocket = new ServerSocket(port);
    System.out.println("自定义Tomcat启动,监听端口:" + port);
    while (true) {
        Socket socket = serverSocket.accept(); // 阻塞等待请求
        new Thread(() -> handleRequest(socket)).start(); // 多线程处理
    }
}

3. 解析请求与封装对象

读取 Socket 输入流,解析请求行信息,模拟HttpServletRequest对象:

private static void handleRequest(Socket socket) {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
        // 解析请求行:GET /login HTTP/1.1
        String requestLine = reader.readLine();
        if (requestLine == null) return;
        String[] parts = requestLine.split(" ");
        String method = parts[0];
        String uri = parts[1];

        // 封装请求/响应对象
        HttpServletRequest req = new MyHttpServletRequest(method, uri);
        HttpServletResponse resp = new MyHttpServletResponse(socket.getOutputStream());

4. 实例化 Servlet 并调用

根据 URI 获取 Servlet 类,实例化后调用service()方法处理请求

        // 获取并实例化Servlet
        Class<? extends HttpServlet> servletClass = SERVLET_MAP.get(uri);
        if (servletClass == null) {
            resp.setStatus(404);
            resp.getWriter().write("404 Not Found");
            return;
        }
        HttpServlet servlet = servletClass.getDeclaredConstructor().newInstance();

        // 调用service方法处理请求
        servlet.service(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5. 封装响应对象

自定义HttpServletResponse实现,将响应数据写入 Socket 输出流:

public class MyHttpServletResponse extends HttpServletResponseWrapper {
    private final OutputStream outputStream;
    public MyHttpServletResponse(OutputStream outputStream) {
        super(null);
        this.outputStream = outputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
    }
}

总结

Servlet 是 HTTP 请求处理的规范组件,通过分层设计降低了开发成本;Tomcat 作为容器,负责 Servlet 的生命周期管理与请求的 “接收 - 解析 - 分发 - 响应” 全流程;自定义 Tomcat 的核心是模拟这一流程,理解其底层逻辑能帮助我们更深入地掌握 Java Web 技术。

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐