手写Tomcat
这段代码是一个简化版的 Tomcat 服务器实现,核心是模拟 Tomcat 接收 HTTP 请求、路由到对应 Servlet、执行 Servlet 逻辑并返回响应的过程。主要流程如下:
- 启动服务器:
Tomcat类的main方法启动,先部署 Web 应用(加载webapps下的应用及其中的 Servlet),再启动ServerSocket监听 8080 端口。 - 接收请求:当客户端(如浏览器)连接时,
ServerSocket接收Socket,并交给线程池中的SocketProcessor处理。 - 解析请求:
SocketProcessor从Socket中读取 HTTP 请求数据,解析出请求方法(如 GET)、URL、协议版本,构造Request对象。 - 路由到 Servlet:根据请求 URL 中的应用名和 Servlet 路径,从
Context(对应 Web 应用)的 Servlet 映射表中找到目标Servlet。 - 执行 Servlet 逻辑:调用
Servlet的service方法(最终会执行doGet/doPost等方法),Servlet处理业务逻辑并向Response写入响应数据。 - 返回响应:
Response把响应行、响应头、响应体按 HTTP 协议格式写入Socket的输出流,返回给客户端.
一、核心类实现逻辑与原理详解
1. AbstractHttpServletRequest 类
package com.example;
// 导入Servlet相关接口和工具类
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;
// 抽象类实现HttpServletRequest接口,作为请求对象的基础
public class AbstractHttpServletRequest implements HttpServletRequest {
// 以下所有方法均为接口方法的空实现
@Override
public String getAuthType() { return ""; } // 返回认证类型,默认空实现
@Override
public Cookie[] getCookies() { return new Cookie[0]; } // 返回Cookie数组,默认空数组
@Override
public long getDateHeader(String s) { return 0; } // 返回日期类型头,默认0
// ... 其余方法均为类似的空实现
}
原理:作为HttpServletRequest接口的适配器,提供基础实现框架。实际请求信息由子类(如Request)通过重写关键方法提供,避免子类需要实现所有接口方法,符合适配器设计模式。
2. AbstractHttpServletResponse 类
package com.example;
// 导入相关类
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
// ... 其他导入
// 抽象类实现HttpServletResponse接口,作为响应对象的基础
public class AbstractHttpServletResponse implements HttpServletResponse {
// 继承Object类的方法
@Override
public int hashCode() { return super.hashCode(); } // 默认哈希实现
// 以下为HttpServletResponse接口方法的空实现
@Override
public void addCookie(Cookie cookie) {} // 添加Cookie,空实现
@Override
public boolean containsHeader(String s) { return false; } // 检查是否包含指定头,默认false
// ... 其余方法均为类似的空实现
}
原理:与请求抽象类类似,为响应对象提供基础框架。子类(如Response)重写关键方法实现响应构建逻辑,减少代码冗余。
3. Context 类
package com.example;
import javax.servlet.Servlet;
import java.util.HashMap;
import java.util.Map;
// 表示Web应用上下文,管理URL与Servlet的映射
public class Context {
private String appName; // 应用名称
// URL模式与Servlet的映射表
private Map<String, Servlet> urlPatternMapping = new HashMap<>();
public Context(String appName) { this.appName = appName; } // 构造方法初始化应用名
// 添加URL模式与Servlet的映射
public void addUrlPatternMapping(String urlPattern, Servlet servlet) {
urlPatternMapping.put(urlPattern, servlet);
}
// 根据URL模式查找对应的Servlet
public Servlet getByUrlPattern(String urlPattern) {
for (String key : urlPatternMapping.keySet()) {
if (key.contains(urlPattern)) { // 简单匹配逻辑
return urlPatternMapping.get(key);
}
}
return null;
}
}
原理:模拟 Tomcat 中的Context组件,负责管理单个 Web 应用的 Servlet 映射关系。通过urlPatternMapping存储 URL 路径与 Servlet 的对应关系,实现请求路由功能。
4. DefaultServlet 类
// 继承 HttpServlet,作为默认 Servlet(当请求未匹配到其他 Servlet 时使用)
public class DefaultServlet extends HttpServlet {
// 无自定义逻辑,使用 HttpServlet 的默认行为
}
作用:当请求的 URL 模式未匹配到任何已注册的 Servlet 时,使用该默认 Servlet 处理请求。
5. Request 类
package com.example;
import javax.servlet.http.HttpServletRequest;
import java.net.Socket;
// 具体请求实现类,封装HTTP请求信息
public class Request extends AbstractHttpServletRequest {
private String method; // 请求方法(GET/POST等)
private String url; // 请求URL
private String protocol; // 协议版本(HTTP/1.1)
private Socket socket; // 底层通信Socket
// 构造方法初始化核心属性
public Request(String method, String url, String protocol, Socket socket) {
this.method = method;
this.url = url;
this.protocol = protocol;
this.socket = socket;
}
// 重写方法返回请求方法
@Override
public String getMethod() { return method; }
// 重写方法返回请求URL
@Override
public StringBuffer getRequestURL() { return new StringBuffer(url); }
// 重写方法返回协议版本
@Override
public String getProtocol() { return protocol; }
// 获取底层Socket
public Socket getSocket() { return socket; }
}
原理:封装从 Socket 中解析出的 HTTP 请求核心信息(方法、URL、协议),提供给后续 Servlet 处理使用。通过继承抽象类,只需要实现关键方法,简化代码。
6.Response 类
package com.example;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
// 具体响应实现类,负责构建和发送HTTP响应
public class Response extends AbstractHttpServletResponse{
private int status = 200; // 响应状态码,默认200
private byte SP = ' '; // 空格分隔符
private byte CR = '\r'; // 回车符
private byte LF = '\n'; // 换行符
private String message = "OK"; // 状态信息
private Map<String,String> headers = new HashMap<>(); // 响应头
private Request request; // 关联的请求对象
private OutputStream socketOutputStream; // Socket输出流
private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream(); // 响应体输出流
// 构造方法初始化请求和输出流
public Response(Request request) {
this.request = request;
try {
this.socketOutputStream = request.getSocket().getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 设置响应状态码和信息
@Override
public void setStatus(int i, String s) {
status = i;
message = s;
}
// 添加响应头
@Override
public void addHeader(String s, String s1) {
headers.put(s, s1);
}
// 获取响应体输出流
@Override
public ResponseServletOutputStream getOutputStream() throws IOException {
return new ResponseServletOutputStream();
}
// 完成响应并发送
public void complete() throws IOException {
sendResponseLine(); // 发送响应行
sendResponseHeader(); // 发送响应头
sendResponseBody(); // 发送响应体
}
// 发送响应体
private void sendResponseBody() throws IOException {
socketOutputStream.write(getOutputStream().getBytes());
}
// 发送响应头
private void sendResponseHeader() throws IOException {
// 自动补充必要的响应头
if (!headers.containsKey("Content-Length")) {
addHeader("Content-Length", String.valueOf(getOutputStream().getPos()));
}
if (!headers.containsKey("Content-Type")) {
addHeader("Content-Type", "text/plain;charset=UTF-8");
}
// 写入所有响应头
for (Map.Entry<String, String> entry : headers.entrySet()) {
socketOutputStream.write(entry.getKey().getBytes());
socketOutputStream.write(":".getBytes());
socketOutputStream.write(entry.getValue().getBytes());
socketOutputStream.write(CR);
socketOutputStream.write(LF);
}
socketOutputStream.write(CR);
socketOutputStream.write(LF); // 响应头结束标志
}
// 发送响应行
private void sendResponseLine() throws IOException {
socketOutputStream.write(request.getProtocol().getBytes());
socketOutputStream.write(SP);
socketOutputStream.write(status); // 注意:此处有bug,状态码应转为字符串
socketOutputStream.write(SP);
socketOutputStream.write(message.getBytes());
socketOutputStream.write(CR);
socketOutputStream.write(LF);
}
}
原理:按照 HTTP 协议规范构建响应内容(响应行→响应头→响应体),并通过 Socket 输出流发送给客户端。核心是维护响应状态、响应头和响应体的结构,确保符合 HTTP 格式要求。
7. ResponseServletOutputStream 类
// 实现 javax.servlet.ServletOutputStream,用于写入响应体数据
public class ResponseServletOutputStream extends ServletOutputStream {
private byte[] bytes = new byte[1024]; // 存储响应体数据的字节数组
private int pos = 0; // 数据写入位置指针
@Override
public void write(int b) throws IOException {
bytes[pos] = (byte) b; // 将数据写入字节数组
pos++;
}
// 获取存储响应体的字节数组
public byte[] getBytes() { return bytes; }
// 获取已写入响应体数据的长度
public int getPos() { return pos; }
// 以下为 ServletOutputStream 接口方法的空实现(非关键逻辑)
@Override
public boolean isReady() { return false; }
@Override
public void setWriteListener(WriteListener writeListener) {}
}
作用:作为响应体的输出流,暂存响应体数据,供 Response 类在发送响应时使用。
8. SocketProcessor 类
package com.example;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
// 处理Socket连接的线程任务类
public class SocketProcessor implements Runnable {
private Socket socket; // 待处理的Socket
private Tomcat tomcat; // Tomcat实例引用
public SocketProcessor(Socket socket, Tomcat tomcat) {
this.socket = socket;
this.tomcat = tomcat;
}
// 线程执行入口
@Override
public void run() {
processSocket(socket); // 处理Socket连接
}
// 处理Socket连接的核心逻辑
private void processSocket(Socket socket) {
try {
InputStream inputStream = socket.getInputStream(); // 获取输入流
byte[] bytes = new byte[1024];
inputStream.read(bytes); // 读取请求数据
// 解析请求方法(GET/POST)
int pos = 0, begin = 0, end = 0;
for (; pos < bytes.length; pos++, end++) {
if (bytes[pos] == ' ') break; // 空格分隔请求方法和URL
}
StringBuilder method = new StringBuilder();
for (; begin < end; begin++) {
method.append((char) bytes[begin]);
}
// 解析URL
pos++;
begin++;
end++;
for (; pos < bytes.length; pos++, end++) {
if (bytes[pos] == ' ') break; // 空格分隔URL和协议
}
StringBuilder url = new StringBuilder();
for (; begin < end; begin++) {
url.append((char) bytes[begin]);
}
// 解析协议版本
pos++;
begin++;
end++;
for (; pos < bytes.length; pos++, end++) {
if (bytes[pos] == '\r') break; // 回车符结束协议版本
}
StringBuilder protocol = new StringBuilder();
for (; begin < end; begin++) {
protocol.append((char) bytes[begin]);
}
// 创建请求和响应对象
Request request = new Request(method.toString(), url.toString(), protocol.toString(), socket);
Response response = new Response(request);
// 路由到对应的Servlet
String requestUrl = request.getRequestURL().toString().substring(1);
String[] parts = requestUrl.split("/");
String appName = parts[0]; // 提取应用名
Context context = tomcat.getContextMap().get(appName); // 获取应用上下文
if (parts.length > 1) {
Servlet servlet = context.getByUrlPattern(parts[1]); // 查找匹配的Servlet
if (servlet != null) {
servlet.service(request, response); // 执行Servlet逻辑
response.complete(); // 发送响应
} else {
new DefaultServlet().service(request, response); // 使用默认Servlet
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
原理:作为线程任务,负责处理单个 Socket 连接的完整生命周期:
- 从 Socket 输入流读取 HTTP 请求数据
- 解析请求方法、URL、协议版本等核心信息
- 创建
Request和Response对象 - 根据 URL 路由到对应的 Web 应用(
Context)和 Servlet - 调用 Servlet 处理逻辑并发送响应
采用线程池 + 任务队列模式(在Tomcat类中实现),支持并发处理多个连接。
9. Tomcat 类
package com.example;
import javax.servlet.Servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 模拟Tomcat服务器的核心类
public class Tomcat {
private Map<String, Context> contextMap = new HashMap<>(); // 应用上下文映射表
// 启动服务器
public void start() {
try {
ExecutorService executorService = Executors.newFixedThreadPool(20); // 创建线程池
ServerSocket serverSocket = new ServerSocket(8080); // 监听8080端口
while (true) { // 循环接受连接
Socket socket = serverSocket.accept(); // 接受客户端连接
executorService.execute(new SocketProcessor(socket, this)); // 提交任务到线程池
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Tomcat tomcat = new Tomcat();
tomcat.deployApps(); // 部署应用
tomcat.start(); // 启动服务器
}
// 部署webapps目录下的所有应用
private void deployApps() {
File webapps = new File(System.getProperty("user.dir"), "webapps"); // 获取webapps目录
for (String app : webapps.list()) { // 遍历应用目录
deployApp(webapps, app);
}
}
// 部署单个应用
private void deployApp(File webapps, String appName) {
Context context = new Context(appName); // 创建应用上下文
File appDirectory = new File(webapps, appName);
File classesDirectory = new File(appDirectory, "classes"); // 类文件目录
List<File> files = getAllFilesPath(classesDirectory); // 获取所有类文件
for (File clazz : files) {
// 解析类的全限定名
String name = clazz.getPath();
name = name.replace(classesDirectory.getPath() + "\\", "");
name = name.replace(".class", "");
name = name.replace("\\", ".");
try {
// 使用自定义类加载器加载类
WebappClassLoader classLoader = new WebappClassLoader(new URL[]{classesDirectory.toURL()});
Class<?> servletClass = classLoader.loadClass(name);
// 判断是否为Servlet
if (HttpServlet.class.isAssignableFrom(servletClass)) {
// 处理@WebServlet注解
if (servletClass.isAnnotationPresent(WebServlet.class)) {
WebServlet annotation = servletClass.getAnnotation(WebServlet.class);
String[] urlPatterns = annotation.urlPatterns(); // 获取URL模式
// 注册Servlet映射
for (String urlPattern : urlPatterns) {
context.addUrlPatternMapping(urlPattern, (Servlet) servletClass.newInstance());
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
contextMap.put(appName, context); // 保存应用上下文
}
// 递归获取目录下所有文件
public List<File> getAllFilesPath(File srcFile) {
List<File> result = new ArrayList<>();
File[] files = srcFile.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
result.addAll(getAllFilesPath(file)); // 递归处理子目录
} else {
result.add(file); // 添加文件
}
}
}
return result;
}
public Map<String, Context> getContextMap() { return contextMap; }
}
原理:模拟 Tomcat 服务器的核心功能:
- 启动服务:创建
ServerSocket监听 8080 端口,通过线程池处理并发连接 - 应用部署:扫描
webapps目录下的应用,加载classes目录中的 Servlet 类 - 类加载:使用自定义
WebappClassLoader加载应用类,模拟 Tomcat 的类加载机制 - Servlet 注册:解析
@WebServlet注解,建立 URL 模式与 Servlet 的映射关系
通过contextMap管理多个 Web 应用,实现类似 Tomcat 的多应用部署能力。
10.WebappClassLoader
package com.example;
import java.net.URL;
import java.net.URLClassLoader;
//自定义类加载器
public class WebappClassLoader extends URLClassLoader {
public WebappClassLoader(URL[] urls) {
super(urls);
}
}
原理:
-
类加载器的基本作用类加载器是 JVM 用于加载
.class文件到内存并生成Class对象的组件。Java 默认的类加载机制遵循双亲委派模型:当加载一个类时,先委托父类加载器加载,只有父类加载器无法加载时,才自己加载。但 Tomcat 为了实现Web 应用间的类隔离(例如不同应用可以使用同一类的不同版本),需要自定义类加载器打破这种默认机制。
-
继承
URLClassLoader的原因URLClassLoader是 JDK 提供的便捷类加载器实现,支持从指定的 URL 路径(如文件系统目录、JAR 包)加载类。对于 Web 应用,类文件通常存放在WEB-INF/classes目录或WEB-INF/lib下的 JAR 中,正好可以通过 URL 路径指定,因此继承URLClassLoader可以简化实现。 -
Web 应用类隔离的实现在示例代码的
Tomcat类中,每个 Web 应用(Context)会创建一个独立的WebappClassLoader实例,其构造参数URL[] urls指向该应用的classes目录(如webapps/myapp/classes)。这样做的效果是:
- 不同 Web 应用的类由各自的
WebappClassLoader加载,即使类名完全相同(如com.example.User),也会被 JVM 视为不同的类(因为类的唯一性由「类全限定名 + 类加载器」共同决定)。 - 避免了不同应用之间的类冲突,例如 A 应用使用
log4j-1.x.jar,B 应用使用log4j-2.x.jar不会互相干扰。
- 不同 Web 应用的类由各自的
-
与 Tomcat 实际类加载机制的对应实际 Tomcat 中的类加载器层级更复杂(如
CommonClassLoader、CatalinaClassLoader、SharedClassLoader等),但核心思想一致:- 每个 Web 应用有独立的
WebappClassLoader。 - 优先加载应用自身的类(打破双亲委派),再委托父类加载器加载公共类(如 JRE 核心类、Tomcat 自身类)。
示例中的
WebappClassLoader是简化版,仅实现了最基础的「从指定路径加载类」功能,未实现完整的双亲委派打破逻辑,但保留了核心设计思想。 - 每个 Web 应用有独立的
总结:WebappClassLoader 的核心作用是为每个 Web 应用提供独立的类加载器,通过指定类加载路径(classes 目录),实现不同应用间的类隔离,避免类冲突。这是 Tomcat 支持多应用部署的关键机制之一,而继承 URLClassLoader 则简化了从文件系统加载类的实现。
11.Servlet 类(业务示例)
package com.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 示例Servlet,处理/test请求
@WebServlet(urlPatterns = {"/test"}) // 映射URL模式
public class Servlet extends HttpServlet {
public Servlet() {} // 构造方法
// 处理GET请求
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getMethod()); // 打印请求方法
resp.getOutputStream().write("hello wanting".getBytes()); // 写入响应内容
}
}
原理:业务逻辑处理类,通过@WebServlet注解声明映射的 URL 模式。继承HttpServlet,重写doGet方法处理 GET 请求,向响应流写入数据。
二、整体运行流程总结
- 启动阶段:
Tomcat类的main方法调用deployApps()部署应用,扫描webapps目录加载 Servlet 并建立映射关系,然后启动ServerSocket监听 8080 端口。 - 请求处理阶段:
- 客户端连接时,
ServerSocket接受Socket并交给线程池的SocketProcessor处理 SocketProcessor解析 HTTP 请求,创建Request和Response对象- 根据 URL 路由到对应 Web 应用的 Servlet,调用其
service方法
- 客户端连接时,
- 响应阶段:Servlet 处理逻辑后,
Response按 HTTP 协议格式组织响应内容,通过 Socket 发送给客户端。
该实现模拟了 Tomcat 的核心功能:请求解析、Servlet 路由、响应构建,简化了实际 Tomcat 中的复杂组件(如 Connector、Engine 等),但保留了核心工作原理。
更多推荐



所有评论(0)