手写一个Tomcat

news2025/3/9 18:21:09

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 技术,并为后续深入学习打下坚实的基础。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312268.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

清华北大推出的 DeepSeek 教程(附 PDF 下载链接)

清华和北大分别都有关于DeepSeek的分享文档&#xff0c;内容非常全面&#xff0c;从原理和具体的应用&#xff0c;大家可以认真看看。 北大 DeepSeek 系列 1&#xff1a;提示词工程和落地场景.pdf  北大 DeepSeek 系列 2&#xff1a;DeepSeek 与 AIGC 应用.pdf  清华 Deep…

用CMake编译glfw进行OpenGL配置,在Visual Studio上运行

Visual Studio的下载 Visual Studio 2022 C 编程环境 GLFW库安装 GLFW官网地址 GLFW官网地址&#xff1a;https://www.glfw.org下载相应版本&#xff0c;如下图&#xff1a; CMake软件进行编译安装 下载CMake 下载的如果是源码包&#xff0c;需要下载CMake软件进行编译安装…

使用MPU6050产生中断,唤醒休眠中的STM32

本篇文章源码&#xff1a;STM32L431_RT_Thread_PM_mpu6050_wakeup: 使用MPU6050产生中断&#xff0c;唤醒休眠中的STM32L4 书接上回【笔记】STM32L4系列使用RT-Thread Studio电源管理组件&#xff08;PM框架&#xff09;实现低功耗-CSDN博客 上一篇文章使用PA0外接一个按键实…

蓝桥杯备赛:炮弹

题目解析 这道题目是一道模拟加调和级数&#xff0c;难的就是调和级数&#xff0c;模拟过程比较简单。 做法 这道题目的难点在于我们在玩这个跳的过程&#xff0c;可能出现来回跳的情况&#xff0c;那么为了解决这种情况&#xff0c;我们采取的方法是设定其的上限步数。那么…

Mysql中的常用函数

1、datediff(date1,date2) date1减去date2&#xff0c;返回两个日期之间的天数。 SELECT DATEDIFF(2008-11-30,2008-11-29) AS DiffDate -- 返回1 SELECT DATEDIFF(2008-11-29,2008-11-30) AS DiffDate -- 返回-1 2、char_length(s) 返回字符串 s 的字符数 3、round(x,d)…

【AD】5-14 多跟走线设置

多跟走线 快捷键UM 先拉出线头并框选或线选&#xff08;快捷键SL&#xff09;&#xff0c;点击交互式总线布线&#xff08;快捷键UM&#xff09;&#xff0c;走线过程中CtrlB调小线间距&#xff0c;shiftB调大线间距或按TAB键直接修改

生物电阻抗技术:精准洞察人体营养的“智能窗口”

生物电阻抗技术&#xff1a;精准洞察人体营养的“智能窗口” 引言&#xff1a;营养监测的新兴力量 在健康管理日益受到重视的今天&#xff0c;人体营养监测成为保障健康的关键环节。 传统营养评估方法往往依赖于主观问卷或侵入性检测&#xff0c;存在诸多局限性。 而生物电阻…

大模型AI平台DeepSeek 眼中的SQL2API平台:QuickAPI、dbapi 和 Magic API 介绍与对比

目录 1 QuickAPI 介绍 2 dbapi 介绍 3 Magic API 介绍 4 简单对比 5 总结 统一数据服务平台是一种低代码的方式&#xff0c;实现一般是通过SQL能直接生成数据API&#xff0c;同时能对产生的数据API进行全生命周期的管理&#xff0c;典型的SQL2API的实现模式。 以下是针对…

快速理清 Attention 注意力和 Encoder, Decoder 概念

之前一直以为 Attention 和 RNN 没关系是凭空蹦出来的新概念&#xff1b;以为 Transformer, Encoder, Decoder 这几个概念是绑在一起的。并不尽然。 Encoder 和 Decoder RNN 里就有 Encoder Decoder 的概念。其中&#xff0c;encoder 接受用户输入&#xff0c;写入 hidden stat…

爬虫案例八js逆向爬取网易音乐

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、js逆向的前期准备二、网站分析三、代码 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 爬取网易音乐 提示&#xff1a;以下是本篇…

Sentinel 笔记

Sentinel 笔记 1 介绍 Sentinel 是阿里开源的分布式系统流量防卫组件&#xff0c;专注于 流量控制、熔断降级、系统保护。 官网&#xff1a;https://sentinelguard.io/zh-cn/index.html wiki&#xff1a;https://github.com/alibaba/Sentinel/wiki 对比同类产品&#xff1…

使用 vxe-table 导出 excel,支持带数值、货币、图片等带格式导出

使用 vxe-table 导出 excel&#xff0c;支持带数值、货币、图片等带格式导出&#xff0c;通过官方自动的导出插件 plugin-export-xlsx 实现导出功能 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.com/x-extends/vxe-table gitee&#xff1a;htt…

powershell@宝塔面板批量建站脚本@批量设置@批量部署伪静态设置

文章目录 abstract批量设置 abstract 对于需要大量建站,并且站点类型都很接近的宝塔用户,可以考虑使用如下powershell脚本进行批量建站语句生成 请根据宝塔的要求的批量建站语句格式创建脚本 例如 function Get-BatchSiteBuilderLines {<# .SYNOPSIS获取批量站点生成器的…

基于multisim的自动干手器设计与仿真

1 设计的任务与要求 设计一个输出 5V 的直流稳压电源。用开关的闭合模拟手挡住光线的功能。用灯的亮灭模拟烘干吹风功能。 2 方案论证与选择 2.1 自动干手器的系统方案 本设计由5V直流电源、红外发射电路、红外接收电路、灯模拟电路构成。 1. 5V直流电源系统 这一部分是整…

webflux响应式编程

webflux&webclient 尚硅谷SpringBoot响应式编程教程&#xff0c;最新springboot3入门到实战 响应式编程设计实战及SpringWebFlux源码剖析 - 拉勾 文章目录 前置知识1、Lambda2、Function3、StreamAPI中间操作&#xff1a;Intermediate Operations终止操作&#xff1a;Ter…

关于tresos Studio(EB)的MCAL配置之GPT

概念 GPT&#xff0c;全称General Purpose Timer&#xff0c;就是个通用定时器&#xff0c;取的名字奇怪了点。定时器是一定要的&#xff0c;要么提供给BSW去使用&#xff0c;要么提供给OS去使用。 配置 General GptDeinitApi控制接口Gpt_DeInit是否启用 GptEnableDisable…

Uniapp项目运行到微信小程序、H5、APP等多个平台教程

摘要&#xff1a;Uniapp作为一款基于Vue.js的跨平台开发框架&#xff0c;支持“一次开发&#xff0c;多端部署”。本文将手把手教你如何将Uniapp项目运行到微信小程序、H5、APP等多个平台&#xff0c;并解析常见问题。 一、环境准备 在开始前&#xff0c;请确保已安装以下工具…

C/C++蓝桥杯算法真题打卡(Day4)

一、P11041 [蓝桥杯 2024 省 Java B] 报数游戏 - 洛谷 算法代码&#xff1a; #include<bits/stdc.h> using namespace std;// 计算第 n 个满足条件的数 long long findNthNumber(long long n) {long long low 1, high 1e18; // 二分查找范围while (low < high) {lo…

C++编写Redis客户端

目录 安装redis-plus-plus库 ​编辑 编译Credis客户端 redis的通用命令使用 get/set exists del keys expire /ttl type string类型核心操作 set和get set带有超时时间 set带有NX string带有XX mset mget getrange和setrange incr和decr list类型核心操作…

记录一下Django的密码重置(忘记密码)

一. Django默认的密码重置 1.路由 # url.pyfrom django.contrib.auth import views as auth_viewsurlpatterns [# 密码重置path(password_reset/, auth_views.PasswordResetView.as_view(), namepassword_reset),# 用户输入邮箱后&#xff0c;跳转到此页面path(password_res…