基于自定义Tomcat实现资源访问的完整指南
文章目录
- 前言:从零构建轻量级Web容器
- 一、环境准备与项目搭建
- 1. 创建Maven项目
- 2. 项目结构
- 二、核心组件实现解析
- 1.HTTP协议处理层
- 2. Servlet容器架构
- 三、注解驱动配置
- 1. 自定义WebServlet注解
- 2. 组件扫描与注册
- 四、服务器核心逻辑
- 1. BIO网络模型实现
- 2. HTTP请求解析
- 3. Servlet路由分发
- 4. 响应处理封装
- 五、开发测试Servlet
- 1. 创建业务Servlet
- 2.运行与测试
- 六、核心原理深度解析
- 总结
前言:从零构建轻量级Web容器
本文将带您实现一个精简版Tomcat的核心功能,重点讲解如何通过Java原生Socket处理HTTP请求、注解驱动配置Servlet、以及实现静态资源访问。通过这个项目,您将深入理解以下核心知识点:
- HTTP协议报文解析原理
- Servlet容器的工作机制
- 反射与注解实现组件扫描
- 阻塞式I/O模型的应用
- Maven项目结构管理
一、环境准备与项目搭建
1. 创建Maven项目
<!-- pom.xml核心依赖 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
2. 项目结构
二、核心组件实现解析
1.HTTP协议处理层
-
报文解析:通过解析HTTP请求首行(GET /path HTTP/1.1),提取请求方法和路径。
-
请求/响应对象:封装HttpServletRequest(存储路径、方法)和HttpServletResponse(操作输出流)。
package com.qcby.webapps.req;
//请求对象
public class HttpServletRequest {
private String path; // 请求路径
private String method; // 请求方式
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
}
//响应对象
import java.io.IOException;
import java.io.OutputStream;
public class HttpServletResponse {
private OutputStream outputStream;
public HttpServletResponse(OutputStream outputStream ){
this.outputStream=outputStream;
}
public void writeServlet(String context) throws IOException{
outputStream.write(context.getBytes());
}
}
2. Servlet容器架构
-
接口分层:
-
Servlet接口定义生命周期方法(init/service/destroy)。
-
GenericServlet提供默认实现,HttpServlet实现HTTP方法分发(doGet/doPost)。
-
-
路由映射:通过路径匹配调用对应的Servlet实例。
package com.qcby.webapps.servlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;
import java.io.IOException;
// Servlet接口定义
public interface Servlet<response> {
public void init();
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException;
public void destroy();
}
// 抽象类实现通用逻辑
public abstract class GenericServlet implements Servlet {
public void init(){
System.out.println("------初始化servlet------");
}
public void destroy(){
System.out.println("------实现servlet对象的销毁------");
}
}
// HTTP方法分发器
public abstract class HttpServlet extends GenericServlet{
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
if(request.getMethod().equals("GET")){
doGet(request,response);
}
else{
doPost(request,response);
}
}
protected abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException;
protected abstract void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException;
}
三、注解驱动配置
1. 自定义WebServlet注解
- 自定义注解:@WebServlet(urlMapping)标记Servlet类并绑定URL路径。
package com.qcby.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//源文件阶段保留
@Target(ElementType.TYPE)//表明@WebSerlet注解是写在类上
public @interface WebServlet {
//String path=null;
String urlMapping() default "" ;
}
2. 组件扫描与注册
- 组件扫描:利用反射扫描类路径,自动注册带注解的Servlet到映射表。
package com.qcby;
import com.qcby.util.SearchClassUtil;
import com.qcby.util.WebServlet;
import com.qcby.webapps.servlet.HttpServlet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ServletConfigMapping {
public static Map<String, HttpServlet> servletMap =new HashMap<>();
static{
List<String> classNames = SearchClassUtil.searchClass();
for (String path:classNames){
try{
Class clazz =Class.forName(path);
WebServlet webServlet =(WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
HttpServlet servlet =(HttpServlet) clazz.newInstance();
servletMap.put(webServlet.urlMapping(), servlet);
System.out.println(servletMap);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
四、服务器核心逻辑
1. BIO网络模型实现
ServerSocket serverSocket = new ServerSocket(8585);
Socket socket = serverSocket.accept(); // 阻塞点
- 阻塞监听:accept()方法阻塞线程直到新连接到达
2. HTTP请求解析
while(true){
int count = 0;
while (count == 0){
count = inputStream.available();
}
// 读取请求数据
byte[] bytes = new byte[count];
inputStream.read(bytes);
String Context = new String(bytes);
// 解析首行
String firstLine = Context.split("\\n")[0];
request.setPath(firstLine.split("\\s")[1]);
request.setMethod(firstLine.split("\\s")[0]);
- 输入流处理:通过inputStream.available()获取数据长度(存在风险)
- 协议解析:按换行符拆分请求头,提取请求方法和路径
3. Servlet路由分发
if(servletMap.containsKey(request.getPath())){
HttpServlet servlet = servletMap.get(request.getPath());
servlet.service(request,response); // 动态请求处理
} else {
// 静态资源处理(待实现)
}
- 映射机制:通过预加载的servletMap实现路径-Servlet绑定
- 责任链模式:Servlet接口统一处理入口
4. 响应处理封装
public class HttpServletResponse {
private OutputStream outputStream;
public void writeServlet(String context) throws IOException {
outputStream.write(context.getBytes());
}
}
- 输出流封装:直接操作Socket的输出流
五、开发测试Servlet
1. 创建业务Servlet
package com.qcby.webapps.myweb;
import com.qcby.util.ResponseUtil;
import com.qcby.util.WebServlet;
import com.qcby.webapps.servlet.HttpServlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;
import java.io.IOException;
@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("我是login的doGet方法");
response.writeServlet(ResponseUtil.getResponseHeader200("hello"));
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
}
}
2.运行与测试
六、核心原理深度解析
- 关键技术点
- 阻塞I/O模型:使用ServerSocket.accept()阻塞等待连接
- 请求解析:通过拆解HTTP报文首行获取关键信息
- 注解驱动:利用反射机制实现组件自动注册
- 资源隔离:静态资源与动态请求分离处理
- 响应封装:规范化HTTP响应头构造
总结
这为我们理解主流Web容器(如Tomcat、Jetty)的工作原理提供了坚实基础。后续可以在此基础上扩展会话管理、JSP支持等高级特性,逐步构建功能完备的Java Web容器。