手写Tomcat:深入理解Servlet容器工作原理

手写Tomcat:深入理解Servlet容器工作原理

在Java Web开发中,Tomcat作为最常用的Servlet容器,其工作原理是每个Java开发人员都应该掌握的核心知识。本文将带领大家通过手写一个简易版的Tomcat,深入剖析Servlet容器的工作原理。我们将按照代码结构,逐个解析每个Java类的实现。

手写Tomcat流程分析图

项目结构概览

1. ServletConfigMapping类 - Servlet配置映射

java 复制代码
package com.qcby.config;

import com.qcby.lib.HttpServlet;
import com.qcby.lib.WebServlet;
import com.qcby.utils.SearchClassUtil;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ServletConfigMapping {
    public static Map<String, HttpServlet> classmapping=new HashMap<>();

    static {
        //通过反射获取路径呗
        List<String>paths= SearchClassUtil.searchClass("com.qcby.webapps.myweb");
        for(String path:paths){
            try {
                Class clazz=Class.forName(path);
                WebServlet webServlet=(WebServlet) clazz.getAnnotation(WebServlet.class);
                String value=webServlet.value();
                HttpServlet httpServlet=(HttpServlet) clazz.getDeclaredConstructor().newInstance();
                classmapping.put(value,httpServlet);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }
}

核心功能

  • 在静态块中初始化Servlet容器
  • 使用反射扫描指定包下的所有类
  • 获取@WebServlet注解中的路径配置
  • 创建Servlet实例并存入HashMap容器

2. GenericServlet类 - Servlet通用抽象类

java 复制代码
package com.qcby.lib;

public abstract class GenericServlet implements Servlet{

    @Override
    public void init() {

    }

    @Override
    public abstract void service(ServletRequest req, ServletResponse resp) throws Exception;

    @Override
    public void destroy() {

    }
}

核心功能

  • 实现了Servlet接口的基本生命周期方法
  • 提供了默认的空实现(init和destroy)
  • 将service方法声明为抽象方法,由子类实现

3. HttpServlet类 - HTTP Servlet抽象类

java 复制代码
package com.qcby.lib;

public abstract class HttpServlet extends GenericServlet{
    public abstract void doGet(ServletRequest req, ServletResponse resp) throws Exception;
    public abstract void doPost(ServletRequest req, ServletResponse resp) throws Exception;

    @Override
    public void service(ServletRequest req, ServletResponse resp) throws Exception {
        if(req.getMethod().equals("GET")){
            this.doGet(req,resp);
            //这个地址就是子类对象的地址
        }else{
            this.doPost(req,resp);
        }
    }
}

核心功能

  • 扩展了GenericServlet,专门处理HTTP协议
  • 定义了doGet和doPost两个抽象方法
  • 实现了service方法,根据请求方法分发到具体的处理方法

4. HttpServletRequest类 - HTTP请求对象

java 复制代码
package com.qcby.lib;

public class HttpServletRequest implements ServletRequest{
    private String method;
    private String path;
    @Override
    public String getMethod() {
        return method;
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public void setMethod(String method) {
        this.method=method;
    }

    @Override
    public void setPath(String path) {
        this.path=path;

    }
}

核心功能

  • 封装HTTP请求信息
  • 提供getter和setter方法访问请求方法和路径
  • 实现了ServletRequest接口

5. HttpServletResponse类 - HTTP响应对象

java 复制代码
package com.qcby.lib;

import java.io.File;
import java.io.OutputStream;

import com.qcby.utils.FileUtil;
import com.qcby.utils.ResponseUtil;

public class HttpServletResponse implements ServletResponse {
    private OutputStream outputStream;

    @Override
    public OutputStream getOutputStream() {
        return this.outputStream;
    }

    @Override
    public void setOutputStream(OutputStream outputStream) {
        this.outputStream=outputStream;
    }



    //还有两个方法 返回静态资源  返回动态资源   说白了都是outputStream.write();  写入输出流进行字节输出  参数是一个字节数组
    @Override
    public void append(String content) throws Exception {
        this.outputStream.write(content.getBytes());
    }
    public void returnStatic(String path) throws Exception {
        //利用file工具包  找到path  在工具包里利用 输出流write
        String resoucePath = FileUtil.getResoucePath(path);
        File file=new File(resoucePath);
        FileUtil.writeFile(file,outputStream);
    }
}

核心功能

  • 封装HTTP响应信息
  • 管理输出流
  • 提供append方法写入动态内容
  • 提供returnStatic方法返回静态资源

6. Servlet接口 - Servlet标准接口

java 复制代码
package com.qcby.lib;

public interface Servlet {
    public void init();
    public void service(ServletRequest req, ServletResponse resp) throws Exception;
    public void destroy();

}

核心功能

  • 定义了Servlet的生命周期方法
  • 标准化Servlet的行为规范

7. ServletRequest接口 - 请求接口

java 复制代码
package com.qcby.lib;

public interface ServletRequest {
    public String getMethod();
    public String getPath();
    public void setMethod(String method);
    public void setPath(String path);
}

核心功能

  • 定义了请求对象的基本操作
  • 提供获取和设置请求方法和路径的方法

8. ServletResponse接口 - 响应接口

java 复制代码
package com.qcby.lib;

import java.io.OutputStream;

public interface ServletResponse {
    public OutputStream getOutputStream();
    public void setOutputStream(OutputStream outputStream);
    public void append(String content) throws Exception;
}

核心功能

  • 定义了响应对象的基本操作
  • 管理输出流和内容写入

9. WebServlet注解 - Servlet配置注解

java 复制代码
package com.qcby.lib;

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 value();  //定义注解的参数吧    传过来的是 访问路径
}

核心功能

  • 运行时保留的注解
  • 只能用于类上
  • 包含一个value参数,用于指定Servlet的访问路径

10. FileUtil类 - 文件工具类

java 复制代码
package com.qcby.utils;

import java.io.*;

/**
 * 该类的主要作用是进行读取文件
 */
public class FileUtil {

    public  static  boolean witeFile(InputStream inputStream, OutputStream outputStream){
        boolean success = false ;
        BufferedInputStream bufferedInputStream ;
        BufferedOutputStream bufferedOutputStream;

        try {
            bufferedInputStream = new BufferedInputStream(inputStream);
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            bufferedOutputStream.write(ResponseUtil.responseHeader200.getBytes());

            int count = 0;
            while (count == 0){
                count = inputStream.available();
            }
            int fileSize = inputStream.available();
            long written = 0;
            int beteSize = 1024;
            byte[] bytes = new byte[beteSize];
            while (written < fileSize){
                if(written + beteSize > fileSize){
                    beteSize = (int)(fileSize - written);
                    bytes = new byte[beteSize];
                }
                bufferedInputStream.read(bytes);
                bufferedOutputStream.write(bytes);
                bufferedOutputStream.flush();
                written += beteSize;
            }
            success = true;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return success;
    }

    public static boolean writeFile(File file,OutputStream outputStream) throws Exception{
        return witeFile(new FileInputStream(file),outputStream);
    }


    public static String getResoucePath(String path){
        String resource = FileUtil.class.getResource("/").getPath();
        return resource + "\\" + path;
    }

}

核心功能

  • 提供文件读取和写入功能
  • 支持大文件分块读取
  • 获取资源文件路径

11. ResponseUtil类 - 响应工具类

java 复制代码
package com.qcby.utils;

public class ResponseUtil {
    public static String responseHeader200 = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html\r\n" +
            "Connection: keep-alive\r\n" +
            "Content-Length: %d\r\n" +
            "\r\n";
    public static String responseHeader404 = "HTTP/1.1 404 NOT FOUND\r\n" +
            "Content-Type: text/html\r\n" +
            "Connection: keep-alive\r\n" +
            "Content-Length: %d\r\n" +
            "\r\n";

    public static String getResponseHeader200(String content){
        return "HTTP/1.1 200 \r\n" +
                "Content-Type:text/html \r\n"+"\r\n" + content;
    }
}

核心功能

  • 提供标准的HTTP响应头
  • 简化响应消息的构建

12. SearchClassUtil类 - 类搜索工具类

java 复制代码
package com.qcby.utils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * 扫描指定包,获取该包下所有的类的全路径信息
 */
public class SearchClassUtil {
    public static List<String> classPaths = new ArrayList<String>();

    public static List<String> searchClass(String path){
        //需要扫描的包名
        String basePack = path;
        //将获取到的包名转换为路径
        String classPath = SearchClassUtil.class.getResource("/").getPath();
        basePack =  basePack.replace(".", File.separator);
        String searchPath = classPath + basePack;
        doPath(new File(searchPath),classPath);
        //这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象
        return classPaths;
    }

    /**
     * 该方法会得到所有的类,将类的绝对路径写入到classPaths中
     * @param file
     */
    private static void doPath(File file,String classpath) {
        if (file.isDirectory()) {//文件夹
            //文件夹我们就递归
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1,classpath);
            }
        } else {//标准文件
            //标准文件我们就判断是否是class文件
            if (file.getName().endsWith(".class")) {
                String path = file.getPath().replace(classpath.replace("/","\\").
                                replaceFirst("\\\\",""),"").replace("\\",".").
                        replace(".class","");
                //如果是class文件我们就放入我们的集合中。
                classPaths.add(path);
            }
        }
    }

    public static void main(String[] args) {
        List<String> classes = SearchClassUtil.searchClass("com.qcby.webapps.myweb");
        for (String s: classes) {
            System.out.println(s);
        }
    }
}

核心功能

  • 递归扫描指定包下的所有.class文件
  • 获取类的全限定名
  • 支持包名到路径的转换

13. MyFirstServlet类 - 第一个Servlet示例

java 复制代码
package com.qcby.webapps.myweb;

import com.qcby.lib.HttpServlet;
import com.qcby.lib.ServletResponse;
import com.qcby.lib.ServletRequest;
import com.qcby.lib.WebServlet;
import com.qcby.utils.ResponseUtil;

@WebServlet("/first")
public class MyFirstServlet extends HttpServlet {
    @Override
    public void doGet(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("接收到了get请求,正在处理");
        resp.append(ResponseUtil.getResponseHeader200("<h1>你好</h1>"));
    }

    @Override
    public void doPost(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("接收到了post请求,正在处理");
        resp.append(ResponseUtil.getResponseHeader200("<h1>你好</h1>"));
    }
}

核心功能

  • 使用@WebServlet注解配置访问路径为"/first"
  • 实现了doGet和doPost方法
  • 处理HTTP GET和POST请求

14. MySecondServlet类 - 第二个Servlet示例

java 复制代码
package com.qcby.webapps.myweb;

import com.qcby.lib.HttpServlet;
import com.qcby.lib.ServletResponse;
import com.qcby.lib.ServletRequest;
import com.qcby.lib.WebServlet;
import com.qcby.utils.ResponseUtil;

@WebServlet("/second")
public class MySecondServlet extends HttpServlet {
    @Override
    public void doGet(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("接收到了get请求,正在处理");
        resp.append(ResponseUtil.getResponseHeader200("<h1>你好</h1>"));
    }

    @Override
    public void doPost(ServletRequest req, ServletResponse resp) throws Exception {
        System.out.println("接收到了Post请求,正在处理");
        resp.append(ResponseUtil.getResponseHeader200("<h1>你好</h1>"));
    }
}

核心功能

  • 使用@WebServlet注解配置访问路径为"/second"
  • 实现了doGet和doPost方法
  • 处理HTTP GET和POST请求

15. MyTomcat类 - Tomcat主启动类

java 复制代码
package com.qcby;

import com.qcby.lib.HttpServlet;
import com.qcby.lib.HttpServletRequest;
import com.qcby.lib.HttpServletResponse;
import com.qcby.utils.ResponseUtil;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import static com.qcby.config.ServletConfigMapping.classmapping;

public class MyTomcat {
    private static HttpServletRequest httpServletRequest=new HttpServletRequest();
    private static HttpServletResponse httpServletResponse=new HttpServletResponse();
    public static final Integer PROT=6677;
    public static void start() throws Exception {
        ServerSocket serverSocket=new ServerSocket(PROT);
        while(true){
            Socket socket = serverSocket.accept(); //得到socket对象
            //获取输入流
            InputStream stream= socket.getInputStream();   //此时是 01
            httpServletResponse.setOutputStream(socket.getOutputStream());
            handler(stream);

        }
    }
    public static void handler(InputStream stream) throws Exception {
        int count=0;
        while(count==0){
            count=stream.available();
        }
        byte[] bytes=new byte[count];
        int read= stream.read(bytes);
        String msg=new String(bytes,0,read);
        //截取
        String firstLine=msg.split("\n")[0];
        String method=firstLine.split("\\s")[0];
        String path=firstLine.split("\\s")[1];
        System.out.println("method:"+method+" path:"+path);
        httpServletRequest.setMethod(method);
        httpServletRequest.setPath(path);
        //对path进行判断
        if(path==""){
            System.out.println("是空请求");
//            httpServletResponse.append(ResponseUtil.responseHeader404);
        }else if(classmapping.get(path)!=null){
            HttpServlet httpServlet=classmapping.get(path);   //我在栈空间设置一个父类引用  指向  子类对象
            httpServlet.service(httpServletRequest,httpServletResponse);
        }else{
            //处理静态资源
            httpServletResponse.returnStatic(path);
        }
    }
    public static void main(String[] args) throws Exception {
        start();
    }
}

核心功能

  • 启动Socket服务器监听6677端口
  • 解析HTTP请求报文
  • 根据请求路径分发到对应的Servlet或静态资源
  • 管理请求-响应处理流程

总结

通过手写这个简易版的Tomcat,我们深入理解了以下核心概念:

  1. Servlet容器本质:一个以URL路径为key,Servlet实例为value的Map
  2. 请求响应流程:Socket接收请求 → 解析HTTP报文 → 路由分发 → 处理并返回
  3. 注解驱动配置:通过反射和注解实现零配置部署
  4. 静态动态资源分离:统一入口,根据路径判断资源类型
    这个简易版Tomcat虽然功能有限,但已经实现了Servlet容器的核心思想。真正的Tomcat在此基础上增加了更多功能,如连接池、会话管理、JSP编译等,但基本原理是相通的。
    希望这篇文章能够帮助你深入理解Tomcat和Servlet容器的工作原理!
相关推荐
Boop_wu1 小时前
[Java EE] 字符流和字节流实例
java·开发语言·apache
是一个Bug1 小时前
Spring事件监听器在电商订单系统中的应用
java·python·spring
Arva .1 小时前
讲一下 Spring 中用到的设计模式
java·spring·设计模式
bbq粉刷匠1 小时前
Java-顺序表
java
Tan_Ying_Y2 小时前
Mybatis的mapper文件中#和$的区别
java·tomcat·mybatis
难以触及的高度2 小时前
Java for循环完全指南:从基础到高性能实践
java·开发语言
sheji34162 小时前
【开题答辩全过程】以 农产品销售系统为例,包含答辩的问题和答案
java·eclipse
budingxiaomoli2 小时前
多线程(三)
java·开发语言
klzdwydz3 小时前
注解与反射
java·开发语言