最通俗易懂的

编程入门 行业动态 更新时间:2024-10-10 00:20:20

最通俗<a href=https://www.elefans.com/category/jswz/34/1769350.html style=易懂的"/>

最通俗易懂的

-- 更新信息 -- 

2023/8/17 更新 - 在第一版代码基础上迎来全新升级

包括将单线程改为多线程,并将项目升级为Maven项目,第二版代码的介绍博客地址:最通俗易懂的 - Tomcat 核心源码仿写 第二版

-- 源码地址 --

朱元杰的开源仓库 -- Tomcat核心源码仿写

-- 正文内容 --

实际上 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 @interface ServletDemo {String path() default "";
}

初级阶段代码

全局静态变量 

    //用于存放全类名private static ArrayList<String> arr=new ArrayList<>();//用于存放Servlet的类对象private static HashMap<String,Class> servletMap=new HashMap<>();

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

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

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

    private static void func(File file) {File[] fs = file.listFiles();String s;for (File f : 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);}}}}
    private static void choseServlet() throws ClassNotFoundException {for(int i=0;i< arr.size();i++){String path=arr.get(i);Class<?> cl=Class.forName(path);if(cl.isAnnotationPresent(ServletDemo.class)){servletMap.put(cl.getAnnotation(ServletDemo.class).path(),cl);}}}

运行阶段

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

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

        //注册端口InetAddress localHost = InetAddress.getLocalHost();System.out.println("localhost:" + localHost);ServerSocket serverSocket = new ServerSocket(8080,10, localHost);//单线程下System.out.println("等待建立连接");Socket server = serverSocket.accept();System.out.println("连接已建立");//定义线程去接收 Http 报文HttpAcceptThread httpAcceptThread=new HttpAcceptThread(server);Thread accept = new Thread(httpAcceptThread);accept.start();accept.join();//处理请求requestHttp(server,httpAcceptThread.strings.get(0));
class HttpAcceptThread implements Runnable{private Socket socket;ArrayList<String> strings=new ArrayList<>();public HttpAcceptThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {System.out.println("开始接收Http");BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String s;while ((s = reader.readLine()).length()!=0){//每次循环接收一行的Http数据try {strings.add(s);System.out.println(s);}catch (Exception e){System.out.println("接收Http进程结束");break;}}System.out.println("接收Http进程结束");} catch (IOException e) {e.printStackTrace();}}
}

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

    private static void requestHttp(Socket socket,String http) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {//获取请求方式String requestStyle=http.split(" ")[0];if(requestStyle.equals("GET")){String httpPathAndParameter=http.split(" ")[1];String httpPath;//创建HttpRequest对象HttpRequestDemo httpRequestDemo=new HttpRequestDemo();if(httpPathAndParameter.indexOf("?")!=-1){httpPath=httpPathAndParameter.substring(1);httpPath=httpPath.split("\\?")[0];System.out.println(httpPath);String parameterString=httpPathAndParameter.split("\\?")[1];String[] parameters=parameterString.split("&");for (int i=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对象OutputStream outputStream=socket.getOutputStream();HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);//反射调用doGetClass<?> servletClass=servletMap.get(httpPath);Method method=servletClass.getMethod("doGet",HttpRequestDemo.class,HttpResponseDemo.class);method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);}else{String httpPath=http.split(" ")[1];httpPath=httpPath.substring(1);System.out.println(httpPath);HttpRequestDemo httpRequestDemo=new HttpRequestDemo();OutputStream outputStream=socket.getOutputStream();HttpResponseDemo httpResponseDemo=new HttpResponseDemo(outputStream);Class<?> servletClass=servletMap.get(httpPath);Method method=servletClass.getMethod("doPost",HttpRequestDemo.class,HttpResponseDemo.class);method.invoke(servletClass.newInstance(),httpRequestDemo,httpResponseDemo);}}

其中一个Servlet的源码如下

import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;@ServletDemo(path = "address1")
public class Servlet1 {public void doGet(HttpRequestDemo request,HttpResponseDemo response) throws IOException {System.out.println("address1 GET响应:");System.out.println("a="+request.getParameter("a"));System.out.println("\n响应的http如下:");String resp= 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();}public void doPost(HttpRequestDemo request,HttpResponseDemo response) throws IOException {System.out.println("\n响应的http如下:");String resp= 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();}}

HttpRequestDemo 如下

import java.util.HashMap;public class HttpRequestDemo {public HashMap<String, String> map = new HashMap<>();public String getParameter(String key) {return map.get(key);}}

 HttpResponseDemo 如下

import java.io.OutputStream;public class HttpResponseDemo {public OutputStream outputStream;public static final String responsebody="HTTP/1.1 200+\r\n"+"Content-Type:text/html+\r\n"+"\r\n";public HttpResponseDemo(OutputStream outputStream){this.outputStream=outputStream;}}

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

朱元杰的开源仓库 -- Tomcat核心源码仿写

效果展示

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

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

并输出了本机的IP地址 169.254.214.160

然后浏览器进行访问

 得到的结果

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

 以及响应的Http报文

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

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

更多推荐

最通俗易懂的

本文发布于:2024-02-07 05:38:17,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1753215.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:易懂   通俗

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!