手写一个Tomcat
 扫测资讯 2025-03-11 12:07   169 0
 扫测资讯 2025-03-11 12:07   169 0Tomcat 是一个广泛使用的开源 Java Servlet 容器,用于运行 Java Web 应用程序。虽然 Tomcat 本身功能强大且复杂,但通过手写一个简易版的 Tomcat,我们可以更好地理解其核心工作原理。本文将带你一步步实现一个简易版的 Tomcat,并深入探讨其核心组件和运行机制。
一、项目概述
Tomcat是一个简易的Java Web服务器,它能够处理HTTP请求并调用相应的Servlet进行处理。项目的 核心功能 包括:
- 
    监听HTTP请求并解析请求内容。 
- 
    根据请求路径调用相应的Servlet。 
- 
    支持GET和POST请求方法。 
- 
    使用注解配置Servlet的URL映射。 
- 
    通过反射机制动态加载Servlet类。 
二、项目结构
首先,我们来看一下项目的整体结构:
项目的主要类及其功能如下:
- 
    ResponseUtil :用于生成HTTP响应头。 
- 
    SearchClassUtil :扫描指定包下的类文件,获取类的全限定名。 
- 
    WebServlet :自定义注解,用于标记Servlet并指定URL映射。 
- 
    LoginServlet 和 ShowServlet :具体的Servlet实现类,处理不同的HTTP请求。 
- 
    HttpServletRequest 和 HttpServletResponse :模拟HTTP请求和响应对象。 
- 
    GenericServlet 和 HttpServlet :抽象类,提供Servlet的基本实现。 
- 
    Servlet :Servlet接口,定义了Servlet的生命周期方法。 
- 
    MyTomcat :主类,负责启动服务器并处理HTTP请求。 
- 
    ServletConfigMapping :维护URL与Servlet的映射关系。 
三、核心组件解析
1、 ResponseUtil 类
   
    
     ResponseUtil
    
   
   类用于生成HTTP响应头。它提供了两个静态方法:
  
- 
    getResponseHeader200(String context):生成状态码为200的HTTP响应头,并将响应内容附加到响应头后。
- 
    getResponseHeader404():生成状态码为404的HTTP响应头。
public class ResponseUtil {
    public static final String responseHeader200 = "HTTP/1.1 200 \r\n" +
            "Content-Type:text/html; charset=utf-8 \r\n" + "\r\n";
    public static String getResponseHeader404() {
        return "HTTP/1.1 404 \r\n" +
                "Content-Type:text/html; charset=utf-8 \r\n" + "\r\n" + "404";
    }
    public static String getResponseHeader200(String context) {
        return "HTTP/1.1 200 \r\n" +
                "Content-Type:text/html; charset=utf-8 \r\n" + "\r\n" + context;
    }
}2、SearchClassUtil 类
   
    
     SearchClassUtil
    
   
   类用于扫描指定包下的类文件,并获取这些类的全限定名。它通过递归遍历目录结构,找到所有以
   
    .class
   
   结尾的文件,并将其路径转换为类的全限定名。
  
public class SearchClassUtil {
    public static List<String> classPaths = new ArrayList<String>();
    public static List<String> searchClass() {
        String basePack = "com.qcby.webapps.myweb";
        String classPath = SearchClassUtil.class.getResource("/").getPath();
        basePack = basePack.replace(".", File.separator);
        String searchPath = classPath + basePack;
        doPath(new File(searchPath), classPath);
        return classPaths;
    }
    private static void doPath(File file, String classpath) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1, classpath);
            }
        } else {
            if (file.getName().endsWith(".class")) {
                String path = file.getPath().replace(classpath.replace("/", "\\")
                                .replaceFirst("\\\\", ""), "").replace("\\", ".")
                        .replace(".class", "");
                classPaths.add(path);
            }
        }
    }
}3. WebServlet 注解
   
    WebServlet
   
   是一个自定义注解,用于标记Servlet类并指定URL映射。它包含一个
   
    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)
public @interface WebServlet {
    String urlMapping() default "";
}4、LoginServlet 和 ShowServlet 类
   
    LoginServlet
   
   和
   
    ShowServlet
   
   是两个具体的Servlet实现类,分别处理
   
    /login
   
   和
   
    /show
   
   路径的请求。它们继承自
   
    HttpServlet
   
   ,并重写了
   
    doGet
   
   方法以处理GET请求。
  
@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 {
    }
}
@WebServlet(urlMapping = "/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("我是show");
        response.writeServlet(ResponseUtil.getResponseHeader200("show"));
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
    }
}5、HttpServletRequest 和 HttpServletResponse
   
    HttpServletRequest
   
   和
   
    HttpServletResponse
   
   类分别模拟了HTTP请求和响应对象。
   
    HttpServletRequest
   
   包含请求路径和请求方法,
   
    HttpServletResponse
   
   包含输出流,用于向客户端发送响应。
  
package com.qcby.webapps.servlet.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;
    }
}
   
    HttpServletRequest
   
   类封装了 HTTP 请求的路径和方法(GET、POST 等)。
  
package com.qcby.webapps.servlet.req;
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());
    }
}
   
    HttpServletResponse
   
   类封装了 HTTP 响应,提供了向客户端输出数据的方法。
  
6. GenericServlet 和 HttpServlet 类
   
    GenericServlet
   
   是一个抽象类,提供了Servlet的基本实现,包括
   
    init
   
   和
   
    destroy
   
   方法。
   
    HttpServlet
   
   继承自
   
    GenericServlet
   
   ,并实现了
   
    service
   
   方法,根据请求方法调用相应的
   
    doGet
   
   或
   
    doPost
   
   方法。
  
public abstract class GenericServlet implements Servlet {
    public void init() {
        System.out.println("------初始化servlet------");
    }
    public void destroy() {
        System.out.println("------实现servlet对象的销毁------");
    }
}
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;
}7. Servlet 接口
   
    Servlet
   
   接口定义了Servlet的生命周期方法,包括
   
    init
   
   、
   
    service
   
   和
   
    destroy
   
   。
  
package com.qcby.webapps.servlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;
import java.io.IOException;
public interface Servlet {
    void init(); // Servlet 初始化
    void service(HttpServletRequest request, HttpServletResponse response) throws IOException; // 处理请求
    void destroy(); // 销毁
}8. MyTomcat 类
   
    MyTomcat
   
   类是项目的核心,负责启动服务器并处理HTTP请求。它通过
   
    ServerSocket
   
   监听指定端口,接收客户端请求,解析请求内容,并根据请求路径调用相应的Servlet。
  
package com.qcby;
import com.qcby.webapps.servlet.HttpServlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import static com.qcby.ServletConfigMapping.servletMap;
public class MyTomcat {
    static HttpServletRequest request = new HttpServletRequest();
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8484);
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            HttpServletResponse response = new HttpServletResponse(outputStream);
            int count = 0;
            while (count == 0) {
                count = inputStream.available();
            }
            byte[] bytes = new byte[count];
            inputStream.read(bytes);
            String context = new String(bytes);
            System.out.println(context);
            if (context.equals("")) {
                System.out.println("你输入了一个空请求");
            } else {
                String firstLine = context.split("\\n")[0];
                request.setPath(firstLine.split("\\s")[1]);
                request.setMethod(firstLine.split("\\s")[0]);
            }
            if (servletMap.containsKey(request.getPath())) {
                System.out.println("存在于HashMap中");
                HttpServlet servlet = servletMap.get(request.getPath());
                servlet.service(request, response);
            } else {
                System.out.println("不存在于HashMap中");
            }
        }
    }
}9. ServletConfigMapping 类
   
    ServletConfigMapping
   
   类维护了URL与Servlet的映射关系。它通过
   
    SearchClassUtil
   
   扫描指定包下的类,利用反射机制获取带有
   
    @WebServlet
   
   注解的类,并将其实例化后存入
   
    servletMap
   
   中。
  
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 = clazz.getDeclaredAnnotation(WebServlet.class);
                HttpServlet servlet = (HttpServlet) clazz.newInstance();
                servletMap.put(webServlet.urlMapping(), servlet);
                System.out.println(servletMap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}四、运行流程
- 
    启动 Tomcat : MyTomcat类的main方法启动,监听 8484 端口。
- 
    接收请求 :当有客户端请求到来时, MyTomcat解析请求的路径和方法。
- 
    分发请求 :根据请求路径从 ServletConfigMapping.servletMap中获取对应的 Servlet 实例,并调用其service方法。
- 
    处理请求 :Servlet 根据请求方法调用 doGet或doPost方法,生成响应并返回给客户端。
通过手写一个简易版的 Tomcat,我们深入理解了 Servlet 容器的工作原理。虽然这个简易版 Tomcat 功能有限,但它涵盖了 Servlet 容器的核心组件和运行机制。希望本文能帮助你更好地理解 Tomcat 和 Servlet 技术,并为后续深入学习打下坚实的基础。

 
 
 
  
  
  
 