news 2026/4/26 2:47:25

# 手写一个迷你Tomcat——三步理解Servlet容器的核心原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
# 手写一个迷你Tomcat——三步理解Servlet容器的核心原理

手写一个迷你Tomcat——三步理解Servlet容器的核心原理

造过轮子的人学框架有多快?我自己写完IOC和AOP,Spring就是换个API。同样的道理,手写一个迷你Tomcat,Tomcat的源码你就看得懂了。

背景

我有一段时间想深入理解Tomcat的原理,看源码一头雾水——Connector、Container、Wrapper、Lifecycle、Pipeline、Valve,概念满天飞。

后来换了个思路:不看了,自己写一个。从最简单的HTTP服务器开始,一步步加功能,每一步都对照Tomcat的设计。

写完之后回头看Tomcat源码,发现它干的也就是这些事——只是做得更完整、更复杂。核心原理,就这么几个。

最终成果

一个能跑的迷你Servlet容器,三步演进:

第一步:HttpServer —— 能返回静态HTML 第二步:HttpServer1 —— 能动态加载并执行Servlet 第三步:HttpServer2 —— 加Facade门面模式,安全隔离

完整代码12个类,总计不到600行。Tomcat的核心思想,全在里面了。

第一步:最简HTTP服务器

目标:浏览器发请求,服务器返回静态HTML文件。

publicclassHttpServer{publicstaticfinalStringWEB_ROOT=System.getProperty("user.dir")+File.separator+"webroot";publicvoidawait(){ServerSocketserverSocket=newServerSocket(80);while(!shutdown){Socketsocket=serverSocket.accept();InputStreaminput=socket.getInputStream();OutputStreamoutput=socket.getOutputStream();Requestrequest=newRequest(input);request.parse();// 解析HTTP请求Responseresponse=newResponse(output);response.setRequest(request);response.sendStaticResource();// 返回静态文件socket.close();}}}

核心流程就四步:accept连接 → 解析请求 → 找文件 → 写回响应

Request的parse方法做的事很暴力——把输入流读成字符串,从中抠出URI:

publicvoidparse(){byte[]buffer=newbyte[2048];inti=input.read(buffer);StringBufferrequest=newStringBuffer(2048);for(intj=0;j<i;j++){request.append((char)buffer[j]);}uri=parseUri(request.toString());}privateStringparseUri(StringrequestString){intindex1=requestString.indexOf(' ');intindex2=requestString.indexOf(' ',index1+1);returnrequestString.substring(index1+1,index2);}

HTTP请求长这样:

GET /index.html HTTP/1.1 Host: localhost

两个空格之间就是URI:/index.html

Response把文件读出来写回Socket:

publicvoidsendStaticResource()throwsIOException{Filefile=newFile(Constants.WEB_ROOT,request.getUri());FileInputStreamfis=newFileInputStream(file);byte[]bytes=newbyte[1024];intch=fis.read(bytes,0,1024);while(ch!=-1){output.write(bytes,0,ch);ch=fis.read(bytes,0,1024);}}

到这里,你已经理解了Tomcat的Connector + 静态资源处理器。Tomcat的DefaultServlet干的也是这个活——根据URI找文件,读出来写回去。

第二步:动态加载Servlet

目标:URL以/servlet/开头时,不是返回文件,而是动态加载并执行一个Servlet类。

这是最关键的一步。Tomcat之所以是"容器",就是因为它能动态加载和执行Servlet。

publicclassHttpServer1{publicvoidawait(){// ... accept连接,解析请求 ...if(request.getUri().startsWith("/servlet/")){ServletProcessor1processor=newServletProcessor1();processor.process(request,response);// 动态加载Servlet}else{StaticResourceProcessorprocessor=newStaticResourceProcessor();processor.process(request,response);// 返回静态文件}}}

路由规则很简单:/servlet/xxx走Servlet处理器,其他走静态资源。

ServletProcessor1的核心——动态加载类并执行:

publicclassServletProcessor1{publicvoidprocess(Requestrequest,Responseresponse){Stringuri=request.getUri();StringservletName=uri.substring(uri.lastIndexOf("/")+1);// 1. 创建URLClassLoader,指向webroot目录URL[]urls=newURL[1];FileclassPath=newFile(Constants.WEB_ROOT);Stringrepository=newURL("file",null,classPath.getCanonicalPath()+File.separator).toString();urls[0]=newURL(null,repository,null);URLClassLoaderloader=newURLClassLoader(urls);// 2. 动态加载Servlet类ClassmyClass=loader.loadClass(servletName);// 3. 实例化并执行service方法Servletservlet=(Servlet)myClass.newInstance();servlet.service((ServletRequest)request,(ServletResponse)response);}}

这三步就是Tomcat加载Servlet的精髓:

步骤我们的代码Tomcat的实现
创建ClassLoaderURLClassLoader指向webrootWebappClassLoader指向WEB-INF/classes和lib
动态加载类loader.loadClass(servletName)同样,但加了缓存和热加载
实例化并执行newInstance() + service()用反射实例化,调用service()

到这里,你已经理解了Tomcat的Servlet容器核心——Wrapper。Tomcat的StandardWrapper就是干这个的:加载Servlet类、实例化、调用service()。

第三步:Facade门面模式——安全隔离

目标:防止Servlet直接访问Request和Response的内部方法。

第二步有一个安全问题:ServletProcessor1把Request对象直接强转成ServletRequest传给了Servlet:

servlet.service((ServletRequest)request,(ServletResponse)response);

问题在哪?Request类实现了ServletRequest接口,但它还有一个parse()方法。Servlet可以通过强转回去调用parse()

// Servlet里写这样的代码:if(requestinstanceofRequest){((Request)request).parse();// 灾难!重新解析了HTTP请求}

这就是安全隐患——Servlet不应该能调用容器内部的方法。

Tomcat的解决方案:Facade门面模式。

publicclassServletProcessor2{publicvoidprocess(Requestrequest,Responseresponse){// ... 加载Servlet类 ...RequestFacaderequestFacade=newRequestFacade(request);ResponseFacaderesponseFacade=newResponseFacade(response);servlet.service((ServletRequest)requestFacade,(ServletResponse)responseFacade);}}

RequestFacade的实现:

publicclassRequestFacadeimplementsServletRequest{privateServletRequestrequest=null;publicRequestFacade(Requestrequest){this.request=request;}publicObjectgetAttribute(Stringattribute){returnrequest.getAttribute(attribute);// 委托给真正的Request}publicStringgetParameter(Stringname){returnrequest.getParameter(name);}// 只有ServletRequest接口定义的方法,没有parse()}

关键区别:

传给Servlet的对象Servlet能调用的方法
Request(第二步)parse()、getUri()、所有ServletRequest方法 + 内部方法
RequestFacade(第三步)只有ServletRequest接口定义的方法

Servlet想强转?转不了——RequestFacade没有parse()方法。就算拿到RequestFacade实例,也调不到内部的Request

这就是Tomcat安全设计的核心思想。Tomcat的org.apache.catalina.connector.RequestFacadeorg.apache.catalina.connector.ResponseFacade就是这么干的,和我们的代码结构一模一样。

对照Tomcat

手写完这三步,再回头看Tomcat的架构:

Tomcat完整架构: Connector(连接器) ├── Http11NioProtocol(处理HTTP请求) ├── 解析请求 → org.apache.coyote.Request └── 交给Container处理 Container(容器) ├── Engine(全局引擎) │ ├── Host(虚拟主机,如localhost) │ │ ├── Context(Web应用,如/myapp) │ │ │ ├── Wrapper(Servlet实例) │ │ │ │ ├── 动态加载Servlet类 │ │ │ │ ├── 实例化并调用service() │ │ │ │ └── Facade门面模式隔离

对应关系:

我们的代码Tomcat对应
HttpServer.await()Connector(接收HTTP连接)
Request.parse()Coyote的HTTP解析器
StaticResourceProcessorDefaultServlet
ServletProcessor2StandardWrapper
URLClassLoaderWebappClassLoader
RequestFacade/ResponseFacadeRequestFacade/ResponseFacade
/servlet/路由Context + Wrapper的URL映射

Tomcat多出来的东西(线程池、Pipeline-Valve、Lifecycle、Session管理、JNDI),都是在这些核心概念上的扩展。核心骨架我们已经写出来了。

这个练习教会我什么

1. Servlet容器本质上就是三件事

  • 接收HTTP请求(ServerSocket + 解析)
  • 路由到处理器(静态文件 or Servlet)
  • 安全隔离(Facade门面模式)

Tomcat百万行代码,最终干的也是这三件事。

2. 动态类加载是容器的灵魂

URLClassLoader.loadClass()这一行代码,就是Tomcat能"热部署"的底层原因——不需要重启JVM,用新的ClassLoader加载新的class文件就行。

Spring的IoC容器、OSGi的模块化、Java的SPI机制,底层都是这个:动态加载类,用接口隔离实现

3. 门面模式不是设计模式的考试题

很多人学门面模式就觉得是"简化接口",但在Servlet容器里,门面模式的目的是安全——防止外部代码访问内部实现。

Tomcat的Facade不是简化,是防御。这个认识不手写一遍代码,看设计模式的书是体会不到的。

和IOC/AOP的呼应

之前写过一篇《造过轮子的人学框架有多快》——自己写完IOC和AOP,Spring就是换个API。

这次又验证了一次:手写完迷你Tomcat,Tomcat源码你就看得懂了。

学框架最好的方式不是读文档、不是看视频,是自己造一个简化版的轮子。造完之后你会发现,那些"高大上"的框架,底层原理你早就在造轮子的时候弄明白了。

区别只在于:框架做得更完整、更健壮、处理了更多边界情况。但核心思想,你的轮子里已经有了。

代码结构

HttpServer/ ├── src/com/my/ │ ├── HttpServer.java # 第一步:静态HTTP服务器 │ ├── HttpServer1.java # 第二步:+ Servlet动态加载 │ ├── HttpServer2.java # 第三步:+ Facade门面模式 │ ├── Request.java # HTTP请求解析 │ ├── Response.java # HTTP响应输出 │ ├── RequestFacade.java # Request的门面 │ ├── ResponseFacade.java # Response的门面 │ ├── ServletProcessor1.java # Servlet处理器(无Facade) │ ├── ServletProcessor2.java # Servlet处理器(有Facade) │ ├── StaticResourceProcessor.java # 静态资源处理器 │ └── Constants.java # 常量定义 └── webroot/ # 静态文件和Servlet类

总结

三步手写迷你Tomcat:

  1. HttpServer:ServerSocket + HTTP解析 + 返回静态文件——这就是Connector
  2. HttpServer1:URLClassLoader动态加载Servlet + service()调用——这就是Wrapper
  3. HttpServer2:RequestFacade/ResponseFacade门面模式——这就是安全隔离

写完这三步,Tomcat的Connector、Container、Wrapper、Facade,不再是概念,而是你亲手写过的代码。

造轮子的意义不是替代框架,是理解框架。理解了,用起来才心里有底。


感谢豆包、智谱、OpenCode在写作过程中的辅助。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 2:45:55

AIGNE DocSmith:基于AI Agent的自动化文档生成系统实战指南

1. 项目概述&#xff1a;当AI成为你的专属文档工程师在技术团队里&#xff0c;写文档这事儿&#xff0c;说多了都是泪。代码迭代飞快&#xff0c;功能日新月异&#xff0c;但文档却总是那个最容易被遗忘的角落。要么是“等代码稳定了再写”&#xff0c;结果一等就是遥遥无期&am…

作者头像 李华
网站建设 2026/4/26 2:41:38

支持向量机(SVM)原理与Python实战指南

1. 支持向量机&#xff1a;机器学习中的边界大师第一次接触支持向量机(SVM)是在处理一个医学图像分类项目时。当时我们尝试了各种分类器&#xff0c;但当数据维度升高到数百维时&#xff0c;只有SVM依然保持着稳定的表现。这种在高维空间中寻找最优决策边界的能力&#xff0c;让…

作者头像 李华
网站建设 2026/4/26 2:40:24

手写一个LRU缓存:从原理到高并发实战

前言你有没有想过&#xff1a;Redis的内存淘汰策略、MySQL的缓冲池、浏览器的后退按钮&#xff0c;背后都用到了同一个算法&#xff1f;LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;。今天&#xff0c;我们手写一个生产级的LRU缓存&#xff1a; O(…

作者头像 李华
网站建设 2026/4/26 2:36:25

从零到精通:AI大模型学习路线全解析!AI大模型学习路线(非常详细)收藏这一篇就够了

本文提供了一份详尽的AI大模型学习路线&#xff0c;涵盖了数学与编程基础、机器学习入门、深度学习深入、大模型探索以及进阶应用等方面。文章推荐了丰富的学习资源&#xff0c;包括经典书籍、在线课程和实践项目&#xff0c;帮助读者系统地学习和掌握AI大模型技术。同时&#…

作者头像 李华
网站建设 2026/4/26 2:35:25

无需照片和 GPU,仅八个问题就能重建 3D 人体模型,效果还超棒!

仅需八个问题&#xff0c;无需照片和 GPU 即可重建 3D 人体模型2026 年 4 月 22 日&#xff0c;工程、机器学习、人体重建、生产领域有了新成果。只需回答八个问题&#xff0c;就能输出 58 个 Anny 人体参数。一个经过物理感知损失函数训练的小型 MLP 模型&#xff0c;能在 CPU…

作者头像 李华