Tomcat 核心源码解析 及 仿写

简介: 介绍Tomcat的核心功能原理,并仿写Tomcat核心功能源码包括:1、如何生成Servlet实例对象并管理2、如何与浏览器建立连接并获取http报文,解析报文获取请求,并响应最后有效果演示和源码gitee地址

实际上 Tomcat 的原理更多的是关于 计算机网络 的知识

下文仅展示 Tomcat 的 核心功能 而不是全部功能

首先回顾Tomcat的核心作用

        1、与浏览器建立连接,接收Http报文,解析获取请求和参数,然后根据请求作响应,最后将结果封装成Http报文响应给浏览器

        2、管理Servlet应用的生命周期

       要完成 第一点功能,需要明白 sockethttp 报文

       socket是网络通信必需的技术,主要作用就是让客户端和服务端建立连接,客户端和服务端都能从该连接中获取输入和输出流,从而进行数据交流

       而http报文就是规定的一种数据格式,规定第几个字节是什么内容,这样双方才能知道对方说的是什么

       要完成 第二点功能,需要明白 JAVA反射,反射就不过多介绍

       

Tomcat为了完成上述功能,需要经历三个阶段

       1、初始化阶段:Web容器加载Servlet类并创建Servlet对象,Servlet容器会运行该对象的init()方法对该对象进行初始化。

       2、运行时阶段:请求到达时,Servlet容器会针对该请求创建ServletRequest对象和ServletResponse对象,然后调用相关Servlet对象的service()方法,service()方法会根据请求调用对应的doGet或doPost等方法。

       3、销毁阶段:Java web应用被终止时,Servlet容器会调用所有Servlet对象的destroy()方法(释放Servlet对象所占用的资源)。


现在我们就依次实现这三个周期(结尾有完整代码)

初始阶段

       在初始阶段,Tomcat需要扫描 Sevlet 所在的目录,获取该目录下的所有 .java 后缀文件,获取这些Java类的全类名,然后通过反射,获取类上的注解,挑选出包含 @Servlet 注解的类,极为Servlet,实际上 @Servlet 可以自己定义,该注解应该至少包含一个属性用于标注Servlet的路径,如下

//该注解可以应用于类、接口(包括注解类型)、枚举@Target(ElementType.TYPE)
//该注解标记的元素可以被Javadoc 或类似的工具文档化@Documented//该注解的生命周期,由JVM 加载,包含在类文件中,在运行时可以被获取到@Retention(RetentionPolicy.RUNTIME)
public@interfaceServletDemo {
Stringpath() default"";
}

image.gif

初级阶段代码

全局静态变量

//用于存放全类名privatestaticArrayList<String>arr=newArrayList<>();
//用于存放Servlet的类对象privatestaticHashMap<String,Class>servletMap=newHashMap<>();

image.gif

main方法内容 ,其中的Servlet所在目录要根据自己的实际情况进行修改

//启动阶段StringinputPath="D:\\javaDemo\\src";     //Servlet所在目录Filefile=newFile(inputPath);        //获取其file对象func(file);      //调用该方法用于获取 .java 后缀文件,并获取全类名System.out.println(arr);
choseServlet();       //调用该方法获取 Servlet 类对象System.out.println(servletMap);

image.gif

main中调用到的全局静态方法

privatestaticvoidfunc(Filefile) {
File[] fs=file.listFiles();
Strings;
for (Filef : fs) {
if (f.isDirectory())    //若是目录,则递归打印该目录下的文件func(f);
if (f.isFile()){
s=f.toString().split("src")[1];
s=s.substring(1);
if(s.length()>=5&&s.substring(s.length()-5).equals(".java")){
s=s.replace('\\','.');
s=s.substring(0,s.length()-5);
arr.add(s);
                }
            }
        }
    }

image.gif

privatestaticvoidchoseServlet() throwsClassNotFoundException {
for(inti=0;i<arr.size();i++){
Stringpath=arr.get(i);
Class<?>cl=Class.forName(path);
if(cl.isAnnotationPresent(ServletDemo.class)){
servletMap.put(cl.getAnnotation(ServletDemo.class).path(),cl);
            }
        }
    }

image.gif

运行阶段

       运行阶段通过socket与浏览器建立连接,然后处理浏览器的请求,并做出响应,为了简单起见,在这我使用了单线程来完成,但真正的Tomcat是多线程的

       下面代码完成的内容是注册端口,建立连接,并接收来之浏览器的Http报文

//注册端口InetAddresslocalHost=InetAddress.getLocalHost();
System.out.println("localhost:"+localHost);
ServerSocketserverSocket=newServerSocket(8080,10, localHost);
//单线程下System.out.println("等待建立连接");
Socketserver=serverSocket.accept();
System.out.println("连接已建立");
//定义线程去接收 Http 报文HttpAcceptThreadhttpAcceptThread=newHttpAcceptThread(server);
Threadaccept=newThread(httpAcceptThread);
accept.start();
accept.join();
//处理请求requestHttp(server,httpAcceptThread.strings.get(0));

image.gif

classHttpAcceptThreadimplementsRunnable{
privateSocketsocket;
ArrayList<String>strings=newArrayList<>();
publicHttpAcceptThread(Socketsocket) {
this.socket=socket;
    }
@Overridepublicvoidrun() {
try {
System.out.println("开始接收Http");
BufferedReaderreader=newBufferedReader(newInputStreamReader(socket.getInputStream()));
Strings;
while ((s=reader.readLine()).length()!=0){
//每次循环接收一行的Http数据try {
strings.add(s);
System.out.println(s);
                }catch (Exceptione){
System.out.println("接收Http进程结束");
break;
                }
            }
System.out.println("接收Http进程结束");
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
}

image.gif

       最后的重点就是对请求作处理了,在这一步中主要是解析Http报文,获取请求方式和请求内容,通过反射调用请求的方法,并且返回结果给浏览器,实际上响应Http报文的封装以及回传应该在Tomcat中完成,这里简单起见,直接在Servlet中完成

privatestaticvoidrequestHttp(Socketsocket,Stringhttp) throwsNoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
//获取请求方式StringrequestStyle=http.split(" ")[0];
if(requestStyle.equals("GET")){
StringhttpPathAndParameter=http.split(" ")[1];
StringhttpPath;
//创建HttpRequest对象HttpRequestDemohttpRequestDemo=newHttpRequestDemo();
if(httpPathAndParameter.indexOf("?")!=-1){
httpPath=httpPathAndParameter.substring(1);
httpPath=httpPath.split("\\?")[0];
System.out.println(httpPath);
StringparameterString=httpPathAndParameter.split("\\?")[1];
String[] parameters=parameterString.split("&");
for (inti=0;i<parameters.length;i++){
httpRequestDemo.map.put(parameters[i].split("=")[0],parameters[i].split("=")[1]);
                }
            }else{
httpPath=httpPathAndParameter.substring(1);
System.out.println(httpPath);
            }
//创建HttpResponse对象OutputStreamoutputStream=socket.getOutputStream();
HttpResponseDemohttpResponseDemo=newHttpResponseDemo(outputStream);
//反射调用doGetClass<?>servletClass=servletMap.get(httpPath);
Methodmethod=servletClass.getMethod("doGet",HttpRequestDemo.class,HttpResponseDemo.class);
method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
        }else{
StringhttpPath=http.split(" ")[1];
httpPath=httpPath.substring(1);
System.out.println(httpPath);
HttpRequestDemohttpRequestDemo=newHttpRequestDemo();
OutputStreamoutputStream=socket.getOutputStream();
HttpResponseDemohttpResponseDemo=newHttpResponseDemo(outputStream);
Class<?>servletClass=servletMap.get(httpPath);
Methodmethod=servletClass.getMethod("doPost",HttpRequestDemo.class,HttpResponseDemo.class);
method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);
        }
    }

image.gif

其中一个Servlet的源码如下

importjava.io.IOException;
importjava.io.OutputStream;
importjava.util.Map;
@ServletDemo(path="address1")
publicclassServlet1 {
publicvoiddoGet(HttpRequestDemorequest,HttpResponseDemoresponse) throwsIOException {
System.out.println("address1 GET响应:");
System.out.println("a="+request.getParameter("a"));
System.out.println("\n响应的http如下:");
Stringresp=HttpResponseDemo.responsebody+"<!DOCTYPE html>\n"+"<html>\n"+"<head>\n"+"    <meta charset=\"utf-8\" />\n"+"</head>\n"+"<body>\n"+" \n"+"    <form name=\"my_form\" method=\"POST\">\n"+"        <input type=\"button\" value=\"按下\" onclick=\"alert('你按下了按钮')\">\n"+"    </form>\n"+" \n"+"</body>\n"+"</html>";
System.out.println(resp);
response.outputStream.write(resp.getBytes());
response.outputStream.flush();
response.outputStream.close();
    }
publicvoiddoPost(HttpRequestDemorequest,HttpResponseDemoresponse) throwsIOException {
System.out.println("\n响应的http如下:");
Stringresp=HttpResponseDemo.responsebody+"{\"sorry\":\"we only respond to method GET now\"},\r\n"+"";
System.out.println(resp);
response.outputStream.write(resp.getBytes());
response.outputStream.flush();
response.outputStream.close();
    }
}

image.gif

HttpRequestDemo 如下

importjava.util.HashMap;
publicclassHttpRequestDemo {
publicHashMap<String, String>map=newHashMap<>();
publicStringgetParameter(Stringkey) {
returnmap.get(key);
    }
}


HttpResponseDemo 如下

importjava.io.OutputStream;
publicclassHttpResponseDemo {
publicOutputStreamoutputStream;
publicstaticfinalStringresponsebody="HTTP/1.1 200+\r\n"+"Content-Type:text/html+\r\n"+"\r\n";
publicHttpResponseDemo(OutputStreamoutputStream){
this.outputStream=outputStream;
    }
}

image.gif

我将代码开源到了 gitee 中,需要的通过以下获取


效果展示

运行tomcatDemo主方法,控制台会输出


这里启动阶段完成,控制台输出了 url 路径对应的 Servlet 类对象

并输出了本机的IP地址 169.254.214.160

然后浏览器进行访问

image.gif编辑

得到的结果


控制台输出接受到的Http请求报文


以及响应的Http报文


并且中间也获取到了 url 中传入的参数


到此一个Tomcat核心功能仿写完成,你也去试试吧 ^-^

目录
相关文章
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
658 29
|
7月前
|
监控 Java 应用服务中间件
Tomcat log日志解析
理解和解析Tomcat日志文件对于诊断和解决Web应用中的问题至关重要。通过分析 `catalina.out`、`localhost.log`、`localhost_access_log.*.txt`、`manager.log`和 `host-manager.log`等日志文件,可以快速定位和解决问题,确保Tomcat服务器的稳定运行。掌握这些日志解析技巧,可以显著提高运维和开发效率。
548 13
|
7月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
188 4
|
7月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
7月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
7月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
8月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
1372 0
|
9月前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
255 0

推荐镜像

更多
  • DNS