当前位置: 首页 > news >正文

手动实现 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 调用。

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;//    存放参数列表 参数名-参数值 =》Hashmapprivate 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=30String 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) {
//                            放入hashmapparametersMapping.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;}@Overridepublic String toString() {return "HspRequest{" +"method='" + method + '\'' +", uri='" + uri + '\'' +", parametersMapping=" + parametersMapping +", inputStream=" + inputStream +'}';}
}

服务端响应模块

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

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 应该具备的核心生命周期方法:

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 容器中起到了业务调度核心枢纽的作用

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 {@Overridepublic 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 响应返回给浏览器

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 {@Overridepublic 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);}}@Overridepublic void doPost(HspRequest request, HspResponse response) {doGet(request, response);}@Overridepublic void init() throws Exception {}@Overridepublic void destroy() {}
}

自定义 Servlet 容器核心类

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

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

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

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解析得到类的全路径
//        定义映射类hashmappublic 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配置
//                     使用反射机制将该实例放入servletMappingSystem.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-mappingSystem.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 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 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>

http://www.lryc.cn/news/582659.html

相关文章:

  • 【AI智能体】智能音视频-硬件设备基于 WebSocket 实现语音交互
  • 一文讲清楚React的diff算法
  • 汽车功能安全系统阶段开发【技术安全方案TSC以及安全分析】5
  • Eigen中Isometry3d的使用详解和实战示例
  • UDP的socket编程
  • 黑马点评系列问题之P37商户点评缓存作业,用了string和list两种方法,可以直接复制粘贴
  • 微软上线Deep Research:OpenAI同款智能体,o3+必应双王炸
  • 专题:2025数据资产AI价值化:安全、战略与应用报告|附400+份报告PDF、原数据表汇总下载
  • openEuler2203sp4-vg磁盘组中剔除磁盘
  • 香港站群服务器与普通香港服务器对比
  • Windows 系统安装与使用 Claude Code 全攻略
  • 【LeetCode 热题 100】142. 环形链表 II——快慢指针
  • OpenWebUI(4)源码学习-后端routers路由模块
  • 车载以太网-TC8测试-UT(Upper Tester)
  • C语言使用Protobuf进行网络通信
  • 2025年微软mos备考攻略-穷鬼版
  • claude code-- 基于Claude 4 模型的智能编程工具,重塑你的编程体验
  • 【DOCKER】-2 docker基础
  • 字符串大小比较的方式|函数的多返回值
  • python transformers库笔记(BertForTokenClassification类)
  • BM10 两个链表的第一个公共结点
  • Linux_常见指令和权限理解
  • OSPFv3与OSPFv2不同点
  • 【Spring WebSocket详解】Spring WebSocket从入门到实战
  • springboot单体项目的发布生产优化
  • 【保姆级目标检测教程】Ubuntu 20.04 部署 YOLOv13 全流程(附训练/推理代码)
  • 基于SpringBoot+Vue的非遗文化传承管理系统(websocket即时通讯、协同过滤算法、支付宝沙盒支付、可分享链接、功能量非常大)
  • 【WEB】Polar靶场 16-20题 详细笔记
  • 从0到1搭建ELK日志收集平台
  • OpenCV探索之旅:形态学魔法