手动实现 Tomcat 核心机制:打造属于自己的 Servlet 容器

在日常 Java Web 开发中,Tomcat 无疑是我们最常接触的 Web 服务器之一。它默默承担了请求解析、Servlet 加载、响应输出等一系列复杂的任务,让开发者可以专注于业务逻辑的实现。然而,你是否曾经好奇:Tomcat 是如何接收浏览器请求并将其交给 Servlet 处理的?Servlet 是在什么时候被加载和执行的?

与其只停留在"使用层面",不如亲手拆解并实现一遍核心机制。本篇博客将带你从零开始动手实现一个简易版的 Servlet 容器,通过手写 socket 通信、URL 映射、Servlet 管理等模块,深入理解 Tomcat 的底层架构与工作原理。

项目技术栈

Maven(项目工具包管理)、网络通信(Socket网络编程)、HTTP协议、IO流、XML解析(DOM4j)、反射机制和servlet规范、BIO多线程模型、模板设计模式。

项目系统框图

系统整体架构代码实现

请求封装模块

将底层 Socket 输入流中的原始 HTTP 请求报文解析成结构化的数据,供 Servlet 调用。

java 复制代码
package http;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;

/**
 * @author xuchuanlei
 * @version 1.0
 * description 封装http请求为我们自定义servlet的请求信息类型(切面编程思想)
 */
public class HspRequest {
    private String method;
    private String uri;

//    存放参数列表 参数名-参数值 =》Hashmap
    private HashMap<String, String> parametersMapping=
        new HashMap<>();
    private InputStream inputStream=null;

//    消息体调用构造器封装
    public HspRequest(InputStream inputStream) {
        this.inputStream = inputStream;

//        封装的具体操作,即解析http请求
        encapHttpRequest();

    }

    private void encapHttpRequest() {
        System.out.println("HspRequest init()");
        try {
//            获取字符流
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(inputStream,"utf-8"));

//          数据示例
//          GET /hspCalServlet?num1=10&num2=30 HTTP/1.1
//          GET /hspCalServlet?num1=10&num2=30 HTTP/1.1
//          GET /?num1=10&num2=10 HTTP/1.1
//          Host: localhost:8080
//          读取第一行,先解析method方法
            String requestLine = bufferedReader.readLine();
            String[] requestLineArr = requestLine.split(" ");
            method = requestLineArr[0];

//            在解析uri有无参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) {
                uri = requestLineArr[1];
            }else {
//                有参数列表进行进一步处理
                uri = requestLineArr[1].substring(0, index);
                //获取参数列表->parametersMapping
                //parameters => num1=10&num2=30
                String parameters = requestLineArr[1].substring(index+1);
                String[] parametersPair = parameters.split("&");
//                鲁棒性的判断
                if (null != parametersPair && !"".equals(parametersPair)) {
//                    再次分割
                    for (String parameterPair : parametersPair) {
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2) {
//                            放入hashmap
                            parametersMapping.put(parameterVal[0], parameterVal[1]);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

//    获取参数值,根据参数名
    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)) {
            return parametersMapping.get(name);
        }else {
            return "";
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public HashMap<String, String> getParametersMapping() {
        return parametersMapping;
    }

    public void setParametersMapping(HashMap<String, String> parametersMapping) {
        this.parametersMapping = parametersMapping;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public String toString() {
        return "HspRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMapping=" + parametersMapping +
                ", inputStream=" + inputStream +
                '}';
    }
}

服务端响应模块

将业务 Servlet 生成的响应内容,通过标准的 HTTP 响应格式写入 Socket 输出流,返回给浏览器客户端。

java 复制代码
package http;

import java.io.OutputStream;

/**
 * @author xuchuanlei
 * @version 1.0
 * description http响应消息的封装
 */
public class HspResponse {
    private OutputStream outputStream=null;
//    写一个消息头
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
        "Content-Type:text/html;charset=utf-8\r\n\r\n";

//    在创建时传入对象,构造器
    public HspResponse(OutputStream outputStream) {
        this.outputStream = outputStream;


    }
    //获取输出流实例,完成消息传输
    public OutputStream getOutputStream() {
        return outputStream;
    }
}

自定义 Servlet 容器中的核心接口定义

定义了一个自定义 Servlet 应该具备的核心生命周期方法:

java 复制代码
package servlet;

import http.HspRequest;
import http.HspResponse;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author xuchuanlei
 * @version 1.0
 * description 定义servlet接口,和init,doGet,doPost方法 (切面编程)
 */
public interface HspServlet {
    public void init() throws Exception;
    public void service(HspRequest request, HspResponse response) throws Exception;
    public void destroy();
}

Servlet 的"模板控制器"

简易 Servlet 容器中起到了业务调度核心枢纽的作用

java 复制代码
package servlet;

import http.HspRequest;
import http.HspResponse;

import javax.servlet.http.HttpServlet;

/**
 * @author xuchuanlei
 * @version 1.0
 * description 抽象类,使用模板设计模式,重写service
 */
public abstract class HspHttpServlet implements HspServlet {
    @Override
    public void service(HspRequest request, HspResponse response) throws Exception {
//        利用多态的动态绑定机制,让doPost和doGet的实现交予我们的业务servlet子类
        if ("GET".equalsIgnoreCase(request.getMethod())) {
            this.doGet(request, response);
        }
        else if ("POST".equalsIgnoreCase(request.getMethod())) {
            this.doPost(request, response);
        }

    }

    public abstract void doGet(HspRequest request, HspResponse response);
    public abstract void doPost(HspRequest request, HspResponse response);

}

业务 Servlet 的核心逻辑处理类

它实现了 HspHttpServlet,即你自定义 Servlet 容器中:

  • 接收并解析客户端请求参数

  • 执行业务计算(num1 + num2)

  • 构建 HTTP 响应返回给浏览器

java 复制代码
package servlet;

import http.HspRequest;
import http.HspResponse;
import utils.WebUtils;

import javax.servlet.http.HttpServlet;
import java.io.IOException;
import java.io.OutputStream;

/**
 * @author xuchuanlei
 * @version 1.0
 * description .......
 */
public class HspCalServlet extends HspHttpServlet {

    @Override
    public void doGet(HspRequest request, HspResponse response) {
//        实现我们的定制servlet逻辑
//        拿到请求并解析
        String num1 = request.getParameter("num1");
        String num2 = request.getParameter("num2");
        int num1_ = WebUtils.parseInt(num1,0);
        int num2_ = WebUtils.parseInt(num2,0);
        int sum = num1_ + num2_;

        OutputStream outputStream = response.getOutputStream();
        String respMes = HspResponse.respHeader
                + "<h1>" + num1 + " + " + num2 + " = " + sum + " HspTomcatV3 - 反射+xml创建</h1>";
        try {

            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void doPost(HspRequest request, HspResponse response) {
        doGet(request, response);
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

自定义 Servlet 容器核心类

实现servlet与多线程关联,同时实现xml的动态类加载(封装+继承+多态+io+dom4j(xml配置文件读取))

  • 请求分发与多线程处理(基于 Socket)

  • 通过解析 XML 配置,实现 Servlet 的类路径注册与动态加载(基于反射)

java 复制代码
package tomcat;


import handler.HspRequestHandler;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import servlet.HspCalServlet;
import servlet.HspHttpServlet;


import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xuchuanlei
 * @version 1.0
 * description 实现servlet与多线程关联,同时实现xml的动态类加载(封装+继承+多态+io+dom4j(xml配置文件读取))
 */
public class HspTomcatV3 {

//        类加载,通过xml解析得到类的全路径
//        定义映射类hashmap
        public static final ConcurrentHashMap<String, HspHttpServlet>
                     servletMapping = new ConcurrentHashMap<>();
        public static final ConcurrentHashMap<String,String>
                servletUrlMapping = new ConcurrentHashMap<>();

    public static void main(String[] args) throws Exception {
        HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
        hspTomcatV3.init();
        hspTomcatV3.run();
    }

    public void run() throws Exception {
        //在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======hsptomcatV2 在8080监听=======");
        //只要 serverSocket没有关闭,就一直等待浏览器/客户端的连接

        while (!serverSocket.isClosed()) {
            //1. 接收到浏览器的连接后,如果成功,就会得到socket
            //2. 这个socket 就是 服务器和 浏览器的数据通道
            Socket socket = serverSocket.accept();
            //3. 创建一个线程对象,并且把socket给该线程
            //  这个是java线程基础
            HspRequestHandler hspRequestHandler =
                    new HspRequestHandler(socket);
            new Thread(hspRequestHandler).start();

        }

    }


//        对容器进行初始化
     public void init() throws Exception {
//        读取xml文件,利用dom4j工具,利用发射机制
        String resource = HspTomcatV3.class.getResource("/").getPath();
        System.out.println(resource);

        SAXReader saxReader = new SAXReader();
         try {
             Document read = saxReader.read(new File(resource + "web.xml"));
             System.out.println("xml=\t"+read);
//                得到根元素
             Element rootElement = read.getRootElement();
//             获取根元素下面的所有子元素
             List<Element> element = rootElement.elements();
//             遍历
             for(Element e:element){

                 if ("servlet".equals(e.getName())){
//                     确保这是一个servlet配置
//                     使用反射机制将该实例放入servletMapping
                     System.out.println("发现现有的servlet!!!!!");
                     Element elementName = e.element("servlet-name");
                     Element elementClass = e.element("servlet-class");


                     System.out.println( "类名:\t"+ elementName.getText());
                     System.out.println("类的全路径:\t"+elementClass.getText());
                     System.out.println("类的全路径:\t"+Class.forName(elementClass.getText().trim()));
                    servletMapping.put
                            (elementName.getText(),
                            (HspHttpServlet)Class.forName(elementClass.getText().trim()).newInstance());

                 } else if ("servlet-mapping".equals(e.getName())){
                     //这是一个servlet-mapping
                     System.out.println("发现 servlet-mapping");
                     Element servletName = e.element("servlet-name");
                     Element urlPatter = e.element("url-pattern");

                     System.out.println("映射名称:\t" + servletName.getText());
                     System.out.println(urlPatter.getText());
                     servletUrlMapping.put(urlPatter.getText(),servletName.getText());

                 }
             }

         } catch (Exception e) {
             throw new Exception(e);
         }

         //老韩验证,这两个容器是否初始化成功
         System.out.println("servletMapping= " + servletMapping);
         System.out.println("servletUrlMapping= " + servletUrlMapping);

     }
}

web.XML文件配置

手动实现的 HspTomcatV3 中,它承担了至关重要的作用:描述 Servlet 的类路径与 URL 映射关系 ,并用于容器启动时通过 DOM4J 解析并完成动态加载。

注意,这里手动实现的XML文件,在web架构下可能不被识别和编译的工作路径下,为此,这里我们考虑直接拷贝.xml文件到target目录。

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<!--    com.xulay.servlet.HspCalServlet-->
    <servlet>
        <servlet-name>HspCalServlet</servlet-name>
        <servlet-class>servlet.HspCalServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HspCalServlet</servlet-name>
        <url-pattern>/hspCalServlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>XCLcalServlet</servlet-name>
        <servlet-class>servlet.XCLcalServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>XCLcalServlet</servlet-name>
        <url-pattern>/XCLcalServlet</url-pattern>
    </servlet-mapping>


</web-app>

项目工具管理Maven配置

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>myTomCat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>

       <dependency>
           <groupId>javax.servlet</groupId>
           <artifactId>javax.servlet-api</artifactId>
           <version>3.1.0</version>
           <scope>provided</scope>
       </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>


</project>
相关推荐
慕y2742 分钟前
Java学习第二十四部分——JavaServer Faces (JSF)
java·开发语言·学习
JosieBook20 分钟前
【Java编程动手学】深入剖析Java网络编程:原理、协议与应用
java·udp·tcp
black_blank21 分钟前
st表 && csp37 第四题 集体锻炼
java·数据结构·算法
我爱Jack23 分钟前
Java List 使用详解:从入门到精通
java·开发语言·数据结构
手握风云-30 分钟前
JavaEE初阶第八期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(六)
java·开发语言
天南星43 分钟前
java-WebSocket在Java生态中的发展历程
java·后端·websocket
chuanauc1 小时前
记录一次在 centos 虚拟机 中 安装 Java环境
java·linux·centos
写不出来就跑路1 小时前
SpringBoot静态资源与缓存配置全解析
java·开发语言·spring boot·spring·springboot
墨着染霜华1 小时前
Caffeine的tokenCache与Spring的CaffeineCacheManager缓存区别
java·spring·缓存
pinlantu2 小时前
Java开发笔记(一百五十五)生成随机数的几种途径
java·eclipse