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

发布于:2024-12-20 ⋅ 阅读:(15) ⋅ 点赞:(0)

目录

一、什么是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) {

    }
}