手写一个迷你Tomcat——三步理解Servlet容器的核心原理造过轮子的人学框架有多快我自己写完IOC和AOPSpring就是换个API。同样的道理手写一个迷你TomcatTomcat的源码你就看得懂了。背景我有一段时间想深入理解Tomcat的原理看源码一头雾水——Connector、Container、Wrapper、Lifecycle、Pipeline、Valve概念满天飞。后来换了个思路不看了自己写一个。从最简单的HTTP服务器开始一步步加功能每一步都对照Tomcat的设计。写完之后回头看Tomcat源码发现它干的也就是这些事——只是做得更完整、更复杂。核心原理就这么几个。最终成果一个能跑的迷你Servlet容器三步演进第一步HttpServer —— 能返回静态HTML 第二步HttpServer1 —— 能动态加载并执行Servlet 第三步HttpServer2 —— 加Facade门面模式安全隔离完整代码12个类总计不到600行。Tomcat的核心思想全在里面了。第一步最简HTTP服务器目标浏览器发请求服务器返回静态HTML文件。publicclassHttpServer{publicstaticfinalStringWEB_ROOTSystem.getProperty(user.dir)File.separatorwebroot;publicvoidawait(){ServerSocketserverSocketnewServerSocket(80);while(!shutdown){SocketsocketserverSocket.accept();InputStreaminputsocket.getInputStream();OutputStreamoutputsocket.getOutputStream();RequestrequestnewRequest(input);request.parse();// 解析HTTP请求ResponseresponsenewResponse(output);response.setRequest(request);response.sendStaticResource();// 返回静态文件socket.close();}}}核心流程就四步accept连接 → 解析请求 → 找文件 → 写回响应。Request的parse方法做的事很暴力——把输入流读成字符串从中抠出URIpublicvoidparse(){byte[]buffernewbyte[2048];intiinput.read(buffer);StringBufferrequestnewStringBuffer(2048);for(intj0;ji;j){request.append((char)buffer[j]);}uriparseUri(request.toString());}privateStringparseUri(StringrequestString){intindex1requestString.indexOf( );intindex2requestString.indexOf( ,index11);returnrequestString.substring(index11,index2);}HTTP请求长这样GET /index.html HTTP/1.1 Host: localhost两个空格之间就是URI/index.html。Response把文件读出来写回SocketpublicvoidsendStaticResource()throwsIOException{FilefilenewFile(Constants.WEB_ROOT,request.getUri());FileInputStreamfisnewFileInputStream(file);byte[]bytesnewbyte[1024];intchfis.read(bytes,0,1024);while(ch!-1){output.write(bytes,0,ch);chfis.read(bytes,0,1024);}}到这里你已经理解了Tomcat的Connector 静态资源处理器。Tomcat的DefaultServlet干的也是这个活——根据URI找文件读出来写回去。第二步动态加载Servlet目标URL以/servlet/开头时不是返回文件而是动态加载并执行一个Servlet类。这是最关键的一步。Tomcat之所以是容器就是因为它能动态加载和执行Servlet。publicclassHttpServer1{publicvoidawait(){// ... accept连接解析请求 ...if(request.getUri().startsWith(/servlet/)){ServletProcessor1processornewServletProcessor1();processor.process(request,response);// 动态加载Servlet}else{StaticResourceProcessorprocessornewStaticResourceProcessor();processor.process(request,response);// 返回静态文件}}}路由规则很简单/servlet/xxx走Servlet处理器其他走静态资源。ServletProcessor1的核心——动态加载类并执行publicclassServletProcessor1{publicvoidprocess(Requestrequest,Responseresponse){Stringurirequest.getUri();StringservletNameuri.substring(uri.lastIndexOf(/)1);// 1. 创建URLClassLoader指向webroot目录URL[]urlsnewURL[1];FileclassPathnewFile(Constants.WEB_ROOT);StringrepositorynewURL(file,null,classPath.getCanonicalPath()File.separator).toString();urls[0]newURL(null,repository,null);URLClassLoaderloadernewURLClassLoader(urls);// 2. 动态加载Servlet类ClassmyClassloader.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传给了Servletservlet.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类 ...RequestFacaderequestFacadenewRequestFacade(request);ResponseFacaderesponseFacadenewResponseFacade(response);servlet.service((ServletRequest)requestFacade,(ServletResponse)responseFacade);}}RequestFacade的实现publicclassRequestFacadeimplementsServletRequest{privateServletRequestrequestnull;publicRequestFacade(Requestrequest){this.requestrequest;}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.RequestFacade和org.apache.catalina.connector.ResponseFacade就是这么干的和我们的代码结构一模一样。对照Tomcat手写完这三步再回头看Tomcat的架构Tomcat完整架构 Connector连接器 ├── Http11NioProtocol处理HTTP请求 ├── 解析请求 → org.apache.coyote.Request └── 交给Container处理 Container容器 ├── Engine全局引擎 │ ├── Host虚拟主机如localhost │ │ ├── ContextWeb应用如/myapp │ │ │ ├── WrapperServlet实例 │ │ │ │ ├── 动态加载Servlet类 │ │ │ │ ├── 实例化并调用service() │ │ │ │ └── Facade门面模式隔离对应关系我们的代码Tomcat对应HttpServer.await()Connector接收HTTP连接Request.parse()Coyote的HTTP解析器StaticResourceProcessorDefaultServletServletProcessor2StandardWrapperURLClassLoaderWebappClassLoaderRequestFacade/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和AOPSpring就是换个API。这次又验证了一次手写完迷你TomcatTomcat源码你就看得懂了。学框架最好的方式不是读文档、不是看视频是自己造一个简化版的轮子。造完之后你会发现那些高大上的框架底层原理你早就在造轮子的时候弄明白了。区别只在于框架做得更完整、更健壮、处理了更多边界情况。但核心思想你的轮子里已经有了。代码结构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类总结三步手写迷你TomcatHttpServerServerSocket HTTP解析 返回静态文件——这就是ConnectorHttpServer1URLClassLoader动态加载Servlet service()调用——这就是WrapperHttpServer2RequestFacade/ResponseFacade门面模式——这就是安全隔离写完这三步Tomcat的Connector、Container、Wrapper、Facade不再是概念而是你亲手写过的代码。造轮子的意义不是替代框架是理解框架。理解了用起来才心里有底。感谢豆包、智谱、OpenCode在写作过程中的辅助。