手写一个Tomcat

发布于:2025-03-10 ⋅ 阅读:(15) ⋅ 点赞:(0)

Tomcat 是一个广泛使用的开源 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的基本实现,包括initdestroy方法。HttpServlet 继承自GenericServlet,并实现了service方法,根据请求方法调用相应的doGetdoPost方法。

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的生命周期方法,包括initservicedestroy

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();
            }
        }
    }
}

四、运行流程

  1. 启动 TomcatMyTomcat 类的 main 方法启动,监听 8484 端口。

  2. 接收请求:当有客户端请求到来时,MyTomcat 解析请求的路径和方法。

  3. 分发请求:根据请求路径从 ServletConfigMapping.servletMap 中获取对应的 Servlet 实例,并调用其 service 方法。

  4. 处理请求:Servlet 根据请求方法调用 doGet 或 doPost 方法,生成响应并返回给客户端。

通过手写一个简易版的 Tomcat,我们深入理解了 Servlet 容器的工作原理。虽然这个简易版 Tomcat 功能有限,但它涵盖了 Servlet 容器的核心组件和运行机制。希望本文能帮助你更好地理解 Tomcat 和 Servlet 技术,并为后续深入学习打下坚实的基础。 


网站公告

今日签到

点亮在社区的每一天
去签到