Tomcat原理(5)——tomcat最终实现

news2025/4/8 19:14:01

目录

一、什么是Servlet容器

二、ServletConfigMapping构建实现容器

ServletConfigMapping

MyTomcat

三、优化server

Server

 MyTomcat

四、匹配

代码如下:

测试如下:


上一篇博客已经为介绍了servelet的实现 ,这篇对上一篇博客进行补充,实现如下流程

一、什么是Servlet容器

        就像上一篇博客说的动态资源映射表,Servlet容器就是一个Key—Value集合。在MyTomcat中我们获取到了注解值Key和类对象的路径。

二、ServletConfigMapping构建实现容器

  • 我通过public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();来定义我们的容器结构。
  • 因为我想让获取key-value信息在我打开tomcat时就加载好,我这里应用了static代码块。代码块会在方法执行前初始化。
  • 在最后再定义一个init方法(为空),我们只需要在Mytomcat主体文件中调用这个方法,就可以完成初始化动作
ServletConfigMapping
package com.qcby.tomcat.config;

import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.webservlet.WebServlet;

import java.io.File;
import java.net.URL;
import java.util.*;

/*
* servlet容器
* */
public class ServletConfigMapping {

    //注意这写HttpServlet,父类对象,因为它要封装一系列子类对象
    public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();
    //static代码块在main方法之前执行
    static {
        try {
        // 1. 扫描包路径 (com.wzh.tomcat.myweb)
        String packageName = "com.qcby.tomcat.MyWeb";
        List<Class<?>> classes = getClasses(packageName);

        // 2. 遍历所有类,检查是否有@WebServlet注解
        for (Class<?> clazz : classes) {
            if (clazz.isAnnotationPresent(WebServlet.class)) {
                // 3. 获取@WebServlet注解的值
                WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
                //System.out.println("类名: " + clazz.getName() + " | URL路径: " + webServlet.path());
                System.out.println(webServlet.path());
                classMap.put(webServlet.path(), (Class<HttpServlet>) clazz);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    }
    private static List<Class<?>> getClasses(String packageName) throws Exception {
        List<Class<?>> classes = new ArrayList<>();
        String path = packageName.replace('.', '/'); // 将包名转换为文件路径

        // 通过类加载器获取包的资源路径
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(path);

        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            File directory = new File(resource.toURI());

            // 扫描文件夹下的所有类文件
            if (directory.exists()) {
                for (File file : directory.listFiles()) {
                    if (file.getName().endsWith(".class")) {
                        // 获取类的完整类名
                        String className = packageName + "." + file.getName().replace(".class", "");
                        classes.add(Class.forName(className));
                    }
                }
            }
        }
        return classes;
    }

    public static void init(){

    }
}
MyTomcat
package com.qcby.tomcat;

import com.qcby.tomcat.config.ServletConfigMapping;

public class MyTomcat {
    public static void main(String[] args) {
        //调用ServletConfigMapping
        ServletConfigMapping.init();
    }

}

调用MyTomcat的main方法

至此,流程实现了一半

 

三、优化server

我们在上一篇博客中单独实现了server的用法,我们现在把这个文件代码优化,并合并到MyTomcat当中

Server

我们把main方法更改为public static void serverInit(),效果和上面ServletConfigMapping是类似的的我们只需要在MyTomcat中调用serverInit()方法,即可实现server服务的打开

package com.qcby.tomcat.socket;

import com.qcby.tomcat.Request.Request;

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

public class Server {
    private static Request request=new Request();

    public static void serverInit() throws IOException {
        // 1.打开通信端口   tomcat:8080   3306  ---------》进行网络通信
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("****************server start.....");

        //2.接受请求数据
        while (true) {
            Socket socket = serverSocket.accept();  //--------------------->注意:此时监听网卡的是:主线程
            System.out.println("有客户进行了链接");
            new Thread(() -> {
                //处理数据---------》数据的处理在于读和写
                try {
                    handler(socket);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    public static void handler(Socket socket) throws Exception {
        //读取请求的数据
        InputStream inputStream = socket.getInputStream();
        requestContext(inputStream);
    }

    public static void requestContext(InputStream inputStream) throws IOException {
        // 创建一个StringBuilder对象,用于构建请求的第一行
        StringBuilder sb = new StringBuilder();
        int context; // 用于存储每次从输入流中读取的单个字节

        // 读取输入流直到遇到换行符(\n)或文件结束(-1)
        while ((context = inputStream.read()) != -1) {
            // 如果读取到换行符,则停止读取
            if (context == '\n') {
                break; // 遇到换行符,退出循环
            }
            // 将读取到的字节转换为字符,并添加到StringBuilder中
            sb.append((char) context);
        }

        // 从StringBuilder中获取第一行字符串,并去除首尾空格
        String firstLine = sb.toString().trim();

        // 检查第一行是否为空
        if (firstLine.isEmpty()) {
            // 如果为空,则打印提示信息
            System.out.println("你输入了一个空请求");
        } else {
            // 如果不为空,则按空格分割第一行字符串为单词数组
            String[] words = firstLine.split("\\s+");
            // 打印出请求方法和请求URI(通常是数组的前两个元素)
            // 注意:这里没有检查数组长度,如果数组长度小于2,将会抛出ArrayIndexOutOfBoundsException
            // 在实际应用中,应该添加适当的错误处理或验证逻辑
            String method=words[0];
            String path=words[2];
            System.out.println(words[0] + " " + words[1]);
            request.setMethod(method);
            request.setPath(path);
        }

    }
}
 MyTomcat
package com.qcby.tomcat;

import com.qcby.tomcat.config.ServletConfigMapping;
import com.qcby.tomcat.socket.Server;

import java.io.IOException;

public class MyTomcat {
    public static void main(String[] args) {
        try {
            //调用ServletConfigMapping,初始化servlet容器
            ServletConfigMapping.init();
            //启动server服务
            Server.serverInit();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

至此,流程又实现了一半

 

四、匹配

        完成上面二三步骤,我们就相当于既得到了servlet容器,又识别出来了http请求要找的对象。现在就是怎么将二者匹配起来。

        在上一篇博客中,我们把所受到的http请求的method和path都封装在了request对象中,所以我们通过调取request进行连接

代码如下:

  如下代码中我把server放在了MyTomcat中。

package com.qcby.tomcat;


import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;
import com.qcby.tomcat.config.ServletConfigMapping;
import com.qcby.tomcat.socket.Server;
import com.qcby.tomcat.webservlet.WebServlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class MyTomcat {
    public static Request request=new Request();
    public static Response response=new Response();
    public static void main(String[] args) throws Exception {
        ServletConfigMapping.init();
        serverInit();

    }

    public static void serverInit() throws Exception{
        // 1.打开通信端口   tomcat:8080   3306  ---------》进行网络通信
        ServerSocket serverSocket = new ServerSocket(8080);//监听8080端口号
        System.out.println("****************server start.....");

        //2.接受请求数据
        while (true){
            Socket socket = serverSocket.accept();  //--------------------->注意:此时监听网卡的是:主线程
            System.out.println("有客户进行了链接");
            new Thread(()->{        //利用子线程方式处理数据
                //处理数据---------》数据的处理在于读和写
                try {
                    handler(socket);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
    public static void handler(Socket socket) throws Exception {
        //读取请求的数据
        InputStream inputStream = socket.getInputStream();
        requestContext(inputStream);
    }
    public static void requestContext(InputStream inputStream) throws IOException, InstantiationException, IllegalAccessException {
        //将bit流转为文字信息
        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];//根据换行来获取第一行数据
            String path=firstLine.split("\\s")[1];
            String method=firstLine.split("\\s")[0];
            System.out.println(path+" "+method);
            request.setMethod(method);
            request.setPath(path);
        }

        dis(request);
    }

    public static void dis(Request request) throws InstantiationException, IllegalAccessException {
        if(!request.getPath().equals("")){//不是空请求
            if(ServletConfigMapping.classMap.get(request.getPath())!=null){//当前请求地址能否从classMap中查找到
                Class<HttpServlet> ClassServlet=ServletConfigMapping.classMap.get(request.getPath());//获取类对象
                HttpServlet servlet=ClassServlet.newInstance();//根据获取到的类对象,创建对应的对象  用父类去接,多态(父类的引用指向子类的对象)
                servlet.service(request,response);//调用HttpServlet的service方法,判断是get请求还是post请求
            }
        }
    }

}
 public static void dis(Request request) throws InstantiationException, IllegalAccessException {
        if(!request.getPath().equals("")){//不是空请求
            if(ServletConfigMapping.classMap.get(request.getPath())!=null){//当前请求地址能否从classMap中查找到
                Class<HttpServlet> ClassServlet=ServletConfigMapping.classMap.get(request.getPath());//获取类对象
                HttpServlet servlet=ClassServlet.newInstance();//根据获取到的类对象,创建对应的对象  用父类去接,多态(父类的引用指向子类的对象)
                servlet.service(request,response);//调用HttpServlet的service方法,判断是get请求还是post请求
            }
        }
    }

 注意!!HttpServlet servlet=ClassServlet.newInstance();是根据获取到的类对象,创建对应的对象  用父类去接,多态(父类的引用指向子类的对象)

方法是对象当中的一块内存空间,这是类对象,不是对象,想要得到信息就得生成对象。

这一句是通过类对象去创建对象。但是因为不确定是请求的哪个对象,需要用父类去承接(多态), 根据对象调用它的doGet或doPost方法。

        整个流程这就走通了。首先当我们启动之后,我们就已经创建好了Servlet的map集合,此时用户的HTTP请求打过来。现在我们处理请求信息,把这些请求信息封装在request对象当中。获取到类对象后,根据获取到的类对象,创建对应的对象。然后再去调用方法。至此成功实现某Servlet的doGet方法。

测试如下:
package com.qcby.tomcat.MyWeb;

import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;
import com.qcby.tomcat.webservlet.WebServlet;

@WebServlet(path ="/MyFirstServlet")
public class MyFirstServlet extends HttpServlet {


    @Override
    public void doGet(Request request, Response response) {
        System.out.println("你好我是FirstServlet");

    }

    @Override
    public void doPost(Request request, Response response) {

    }
}

 

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

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

相关文章

echarts 常见组件合集

仪表盘组件 <template><div class"w100 h100" ref"chart"></div> </template><script> import resize from "./mixins/resize"; export default {mixins: [resize],props: ["list"],watch: {list: {// …

C/C++代码性能优化技巧的书籍及资料

使用C/C开发的场景&#xff0c;大多对代码的执行的速度&#xff0c;实时性有较高的要求&#xff0c;像嵌入式系统的开发&#xff0c;资源还受限。在算力存储空间有限的MCU上写出简洁又高效的代码实际是一种艺术。软件工程师在代码设计上的这种差距&#xff0c;会反映在产品的性…

FlightAD 解读

一 文章相关信息 出处&#xff1a;ICPADS CCF C 会议&#xff08;代码未开源&#xff09;&#xff0c;COUTA 研究团队的另一研究 二 Methodology 1. 整体架构&#xff1a; 2. Multi-Scale Sampling&#xff08;多尺度&#xff09; 实际上&#xff0c;就是对每个单通道作 “多…

20241216软考架构-------软考案例23答案

每日打卡题案例23 23.【2015年真题】 难度&#xff1a;一般 阅读以下关于系统设计建模的说明&#xff0c;回答下列问题。&#xff08;共25分&#xff09; 【说明】 某公司拟研制一款高空监视无人直升机&#xff0c;该无人机采用遥控一自主复合型控制实现垂直升降。该直升机飞行…

170页ppt解读如何进行大型集团信息安全管理体系优化咨询

文档为甲方集团信息安全管理体系优化咨询项目的信息安全建设规划报告&#xff0c;重点围绕信息安全建设的规划与设计展开。报告首先进行了信息安全建设需求分析&#xff0c;明确了当前信息安全现况存在的问题、信息安全发展趋势及具体需求汇整&#xff0c;为后续建设提供了坚实…

RK3576 Android14,内存大于4G时UVC应用无法申请内存

最近有个项目需要将Linux虚拟成UVC摄像头&#xff0c;开发过程中遇到一个奇怪的事情&#xff0c;通过V4l2框架接口申请内存时&#xff0c;相同的板子&#xff0c;只是内存一个4G一个8G。4G的内存可以申请成功&#xff0c;8G就不行。提示“内存不足” 内存更大反而内存不足&…

TimesFM(Time Series Foundation Model)时间序列预测股市价格的数据研究(4)

TimesFM&#xff08;Time Series Foundation Model&#xff09;时间序列预测的数据研究(3)-CSDN博客文章浏览阅读846次&#xff0c;点赞19次&#xff0c;收藏12次。1. **表示预测区间**&#xff1a;在很多预测任务中&#xff0c;模型给出的不只是一个单一的预测值&#xff08;比…

opencv所有常见函数

一、opencv图像操作 二、opencv图像的数值运算 三、opencv图像的放射变换 四、opencv空间域图像滤波 五、图像灰度化与直方图 六、形态学图像处理 七、阈值处理与边缘检测 八、轮廓和模式匹配

常见漏洞—SSRF_FastCGI

FastCGI协议 简介 Fast CGI源自旧版本的CGI 路由/结构图 # 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求&#xff08;例如nginx&#xff09; web server 是内容的分发者 当访问静态页面时&#xff0c;web server 会直接返回资源&#xff0c;例如index.htm…

【游戏设计原理】10 - 科斯特的游戏理论

科斯特的游戏理论强调了游戏与学习之间的关系&#xff0c;认为“玩得开心”与“学习”是紧密相连的。换句话说&#xff0c;游戏的核心魅力在于通过适当的挑战和不断的学习进程激发玩家的内啡肽循环&#xff0c;这让玩家在不断的探索和进步中找到乐趣。 科斯特的理论通过游戏是…

ES-IndexTemplate和DynamicTemplate

IndexTemplate 什么是IndexTemplate 索引模板&#xff0c;帮助你设定Mappings和Settings&#xff0c;并按照一定的规则&#xff0c;自动匹配到新创建的索引之上 模板仅在一个索引被新建的时候&#xff0c;才会产生应用&#xff0c;索引被修改不会影响已创建的索引可以设定多…

【大语言模型】ACL2024论文-27 Mementos:一个全面的多模态大型语言模型在图像序列推理上的基准测试

【大语言模型】ACL2024论文-27 Mementos&#xff1a;一个全面的多模态大型语言模型在图像序列推理上的基准测试 目录 文章目录 【大语言模型】ACL2024论文-27 Mementos&#xff1a;一个全面的多模态大型语言模型在图像序列推理上的基准测试目录文章摘要研究背景问题与挑战如何…

CSS基础与应用详解

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Css篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Css篇专栏内容:CSS基础与应用详解 前言 CSS&#xff08;层叠样式表&#xff09;是网页设计中不可或缺的一部分&am…

C/S软件授权注册系统(Winform+WebApi+.NET8+EFCore版)

适用软件&#xff1a;C/S系统、Winform桌面应用软件。 运行平台&#xff1a;Windows .NETCore&#xff0c;.NET8 开发工具&#xff1a;Visual Studio 2022&#xff0c;C#语言 数据库&#xff1a;Microsoft SQLServer 2012&#xff0c;Oracle 21c&#xff0c;MySQL8&#xf…

国标GB28181网页直播平台EasyGBS国标EasyGBD对讲音频demo

近年来&#xff0c;随着信息技术的飞速发展&#xff0c;视频监控领域正经历从传统安防向智能化、网络化安防的深刻转变。在此过程中&#xff0c;GB28181标准凭借其强大的功能和灵活性&#xff0c;成为了推动视频监控系统互联互通和高效管理的重要一环。通过支持GB28181协议&…

session 共享服务器

1.安装 kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc9-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar …

【蓝桥杯国赛真题15】python质因数个数 蓝桥杯青少年组python编程国赛真题详细解析

目录 python质因数个数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python质因数个数 第十二届蓝桥杯青少年组python比赛国赛真题详细解析 …

智能硬件「百团大战」:AI驱动的周期来了吗?

要想在竞争激烈的市场中打造出真正的AI硬件“爆款”&#xff0c;并非简单地在现有硬件上堆砌AI功能就能实现&#xff0c;而是需要深刻理解AI的本质&#xff0c;用AI技术从底层逻辑出发&#xff0c;彻底重塑硬件产品的设计、功能与用户体验。 作者|斗斗 编辑|皮爷 出品|产…

Linux核心概念与常用命令

文章目录 一、Linux概述1、常见的操作系统2、Linux发展史3、Linux目录结构 二、文件和目录操作1、pwd - 显示当前目录2、cd - 切换目录3、ls - 列出目录内容4、mkdir - 创建目录5、touch - 创建空文件6、cp - 复制文件或目录7、mv - 移动或重命名文件8、rm - 删除文件或目录9、…

uniappp配置导航栏自定义按钮(解决首次加载图标失败问题)

1.引入iconfont的图标&#xff0c;只保留这两个文件 2.App.vue引入到全局中 import "./static/fonts/iconfont.css"3.pages.json中配置text为图标对应的unicode {"path": "pages/invite/invite","style": {"h5": {"…