Tomcat 核心源码解析 及 仿写

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 介绍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核心功能仿写完成,你也去试试吧 ^-^

目录
相关文章
|
19天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
39 0
|
19天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
29 0
|
19天前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
33 0
|
19天前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
44 0
|
16天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
39 5
|
18天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
18天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
19天前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
29 0
|
19天前
|
Java 容器
Collection-LinkedList源码解析
Collection-LinkedList源码解析
11 0
|
19天前
|
存储 Java 编译器
Collection-ArrayList源码解析
Collection-ArrayList源码解析
13 0

推荐镜像

更多