在写完《Java 网络编程原理 - 应用程序是如何建立 TCP 连接并响应网络请求的?》 一文以后,也是了解了 Tomcat 的底层原理,同时掌握了网络通信的原理和各种序列化方式,我就想着如何自己实现一个 Tomcat web 容器呢?
核心功能是来接收浏览器的请求,同时根据请求过来的信息,进行路由到接口上进行数据处理,根据最后处理的数据结果进行响应。

我得构架设计思路图如下,实现一个简单的 Web 容器:

通过 main 方法进行启动,结合 ServerSocket 创建一个 TCP 服务器,监听 8080 端口,通过多线程处理多个客户端连接,当然也可以改用 NIO 模式,一个线程足以。
handleClient 方法中是对客户端的数据流进行读取和响应,通过输入流读取 HTTP 请求,封装成自定义的 MyServletRequest 请求对象。
java/**
* Web 服务器
*
* @author Created by Liushigong on 2025-12-24
*/
public class WebTcpServer {
/**
* 服务器监听的端口
*/
private static final int SERVER_PROT = 8080;
/**
* 线程池,用于处理客户端连接
*/
private static final Executor executor = Executors.newFixedThreadPool(10);
/**
* 启动服务
*/
public static void start() {
// 创建服务器Socket
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(SERVER_PROT);
// 持续监听新的连接
while (true) {
// 阻塞,直到有客户端连接
Socket clientSocket = serverSocket.accept();
// 为每个客户端连接创建一个新的线程来处理,避免阻塞主线程
executor.execute(() -> handleClient(clientSocket));
}
} catch (IOException e) {
System.err.printf("服务器启动或运行异常: %s\n", e.getMessage());
} finally {
// 关闭服务器socket
if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close();
System.out.println("服务器已关闭。");
} catch (IOException e) {
System.err.printf("关闭服务器socket时发生错误: %s\n", e.getMessage());
}
}
}
}
private static void handleClient(Socket clientSocket) {
try (
// 使用 try-with-resources 确保流自动关闭
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
OutputStream clientOut = clientSocket.getOutputStream();
) {
// 构建请求对象
MyServletRequest request = new MyServletRequest();
// 读取客户端消息
String clientMessage;
// 持续读取客户端消息
while ((clientMessage = in.readLine()) != null) {
// 解析请求成功后,返回响应
if (parse(request, clientMessage)) {
// 调用请求接口
MyServletResponse response = MappingFactory.invoke(request);
// 发送HTTP响应头
clientOut.write(response.getResponseHeaders().getBytes(StandardCharsets.UTF_8));
// 发送响应体
clientOut.write(response.getResponseData());
clientOut.flush();
break;
}
;
}
} catch (IOException e) {
System.err.printf("处理客户端连接时发生错误: %s\n", e.getMessage());
} finally {
try {
// 关闭客户端socket
clientSocket.close();
} catch (IOException e) {
System.err.printf("关闭客户端socket时发生错误: %s\n", e.getMessage());
}
}
}
public static boolean parse(MyServletRequest request, String message) {
// 当前线程的对象
if (message.contains("GET") || message.contains("POST")) {
String data = message.substring(message.indexOf("/"), message.indexOf(" HTTP/"));
request.setPath(data);
} else if (message.contains("Host")) {
String data = message.substring(message.indexOf(" ") + 1);
request.setHost(data);
} else if (message.contains("sec-ch-ua-platform")) {
String data = message.substring(message.indexOf("\"") + 1, message.lastIndexOf("\""));
request.setPlatform(data);
}
// 遇到空行,表示HTTP头部结束
return message.isEmpty();
}
public static void main(String[] args) {
// 初始化控制器
WebController.init();
// 启动服务
start();
}
}
映射器工厂中存储了 url 与控制器对象和方法的缓存信息,当数据流读取完成以后,根据请求路径从 Map 缓存中查找对应的 Servlet 进行反射调用,获取响应数据进行返回。
javaimport java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 映射器工厂
*
* @author Created by Liushigong on 2025-12-24
*/
public class MappingFactory {
/**
* url 与 方法映射
*/
private static final Map<String, Method> mapping = new ConcurrentHashMap<>();
/**
* url 与 对象映射
*/
private static final Map<String, Object> objectMap = new ConcurrentHashMap<>();
/**
* 注册映射关系
*
* @param path url
* @param object 对象
* @param method 方法
*/
public static void register(String path, Object object, Method method) {
mapping.put(path, method);
objectMap.put(path, object);
}
public static Method get(String path) {
return mapping.get(path);
}
/**
* 调用方法
*
* @param request 请求信息
* @return 响应信息
*/
public static MyServletResponse invoke(MyServletRequest request) {
// 请求信息
MyServletResponse response = new MyServletResponse();
if (request.getPath() == null) {
response.setStatus(400);
response.setBody("<html><body><h1>400 Bad Request</h1></body></html>");
}
else if (!objectMap.containsKey(request.getPath())) {
response.setStatus(404);
response.setBody("<html><body><h1>404 Not Found</h1></body></html>");
}
else {
try {
// 调用方法
Object body = get(request.getPath()).invoke(objectMap.get(request.getPath()), request);
response.setBody(String.valueOf(body));
} catch (Exception e) {
response.setStatus(500);
response.setBody("<html><body><h1>500 Internal Server Error</h1></body></html>");
}
}
return response;
}
}
定义 @WebPath 注解用来标识控制器的接口信息,此处在启动时直接调用 init() 初始化控制器的方法,到映射器工厂中进行缓存。
javaimport cn.hutool.core.util.ReflectUtil;
import java.lang.reflect.Method;
import java.util.List;
/**
* 自定义控制器
*
* @author Created by Liushigong on 2025-12-24
*/
public class WebController {
@WebPath(path = "/test")
public String test(MyServletRequest request) {
return request.getPath() + " 调用成功,噢耶!";
}
@WebPath(path = "/test1")
public String test1(MyServletRequest request) {
return request.getPath() + " 调用成功,噢耶!";
}
@WebPath(path = "/test2")
public String test2(MyServletRequest request) {
return request.getPath() + " 调用成功,噢耶!";
}
@WebPath(path = "/test3")
public String test3(MyServletRequest request) {
throw new RuntimeException("测试异常");
}
/**
* 初始化控制器
*/
public static void init(){
// 当前对象
WebController webController = new WebController();
// 获取所有带 @WebPath 注解的方法
List<Method> publicMethods = ReflectUtil.getPublicMethods(WebController.class, method -> method.isAnnotationPresent(WebPath.class));
for (Method method : publicMethods) {
WebPath webPath = method.getAnnotation(WebPath.class);
// 注册到映射器工厂
MappingFactory.register(webPath.path(), webController, method);
}
}
}
自定义注解,用来标识接口信息
java@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface WebPath {
/**
* URL路径地址
*/
String path();
}
自定义请求对象
java@Data
public class MyServletRequest {
/**
* 请求路径
*/
private String path;
/**
* 请求主机
*/
private String host;
/**
* 请求平台
*/
private String platform;
}
自定义的响应体对象
javaimport lombok.Data;
import java.nio.charset.StandardCharsets;
/**
* @author Created by Liushigong on 2025-12-24
*/
@Data
public class MyServletResponse {
/**
* 响应状态码
*/
private int status = 200;
/**
* 响应体类型
*/
private String contentType = "text/html;charset=UTF-8";
/**
* 响应体
*/
private String body;
/**
* 获取响应体字节数组
*/
public byte[] getResponseData() {
return body.getBytes(StandardCharsets.UTF_8);
}
/**
* 生成HTTP响应头
*/
public String getResponseHeaders() {
// 空行分隔头和体
return "HTTP/1.1 " + status + " OK\r\n" +
"Content-Type: " + contentType + "\r\n" +
"Content-Length: " + getResponseData().length + "\r\n" +
"Connection: close\r\n" +
"\r\n";
}
}
当页面上访问 http://localhost:8080/test 时,效果如下

访问 http://localhost:8080/test3 时,效果如下

访问 http://localhost:8080/test4 时,效果如下

短短几个类就能实现这么一个轻量级的web服务器,整体效果还是很不错的。
上述实现省略了 Servlet 解析的过程,直接通过 WebController.init() 进行的初始化,扩展性上我并没有做太多处理,这主要是因为当前我并没有交给 Spring IOC 容器管理 Bean 对象。
如果想要集成 Spring,可以依赖 spring-context、spring-core 等jar包,以下是启动IOC容器的方式:
java// 启动 IOC 容器
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.refresh();
将 Servlet 注册成 Bean 对象以后,可以结合 BeanPostProcessor #postProcessAfterInitialization 后置处理,自定义个 @Controller 一类的注解,用来标识是控制器,然后自动解析请求接口,进行映射器自动注册的操作,后续通过请求进行分发路由进行调用。
像接口的拦截器,参数解析器和结果响应处理器等都可以进行补充,有兴趣的小伙伴可以看下我画的 Spring MVC 的执行流程图,自行扩展实现。

本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!