Tomcat原理(6)——tomcat完整实现

Tomcat原理(5)------tomcat最终实现-CSDN博客文章浏览阅读610次,点赞7次,收藏17次。就像上一篇博客说的动态资源映射表,Servlet容器就是一个Key---Value集合。在MyTomcat中我们获取到了注解值Key和类对象的路径。https://blog.csdn.net/2301_78566776/article/details/144515542?spm=1001.2014.3001.5501 之前的博客介绍到使用我们 自己做的tomcat访问动态资源,这篇博客介绍如何实现动静态资源一同访问,完善之前的结构。

目录

一、创建项目

二、放置静态资源

三、逐步配置tomcat

1.创建webservlet注解类

2.创建request类

3.创建response类

4.导入工具包

5.创建HttpServlet抽象类

6.创建Servlet容器

7.MyWeb放置动态资源

8.MyTomcat核心类

四、结果展示:


一个完整的交互过程如上图,这篇博客为总结,将MyTomcat进行一个完整实现

一、创建项目

我们首先选择maven创建一个maven项目

创建完成!

二、放置静态资源

静态资源如html等,我们把它放在是src.main.resources下

我这里新建一个Demo.html界面

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

  <span style="font-size:40px">"Hello,it is Demo"</span>

</body>
</html>

三、逐步配置tomcat

上一篇博客里有详细讲解,下面我们简单的逐步配置一下

1.创建webservlet注解类
java 复制代码
package com.qcby.tomcat.webservlet;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface WebServlet {

    String path() default "";
}
2.创建request类
java 复制代码
package com.qcby.tomcat.Request;

public class Request {
    private String path;
    private String method;

    public Request(String path, String method) {
        this.path = path;
        this.method = method;
    }

    public Request() {

    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }
}
3.创建response类

放入后报错先不要急,之后会导入工具包

java 复制代码
package com.qcby.tomcat.Response;

import com.qcby.tomcat.util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class Response {
    private OutputStream outputStream;

    //打开输出流
    /*
    * 输入输出流都来源于socket,那么这里初始化的outputStream究竟是什么
    * 就取决于MyTomcat中传进来的socket.getOutputStream是什么了
    * */
    public Response(OutputStream outputStream){
        this.outputStream=outputStream;
    }

    //静态资源进行输出
    public void wirthHtml(String path) throws Exception {
         /*
        getResoucePath()是工具类FileUtil中获取完整路径的方法
        其内部主要用了getResource()方法--根据当前类的位置来定位资源文件
        */

        String resourcePath=FileUtil.getResoucePath(path);
        System.out.println("resourcePath:"+resourcePath);
        File file=new File(resourcePath);
        if(file.exists()){ //访问的静态资源文件是存在的
            System.out.println("静态资源存在");
            FileUtil.writeFile(file,outputStream);
        }else {
            System.out.println("404");
        }
    }

    public  void wirth(String context) throws IOException {
        outputStream.write(context.getBytes(StandardCharsets.UTF_8));
    }

}

解释

response是做输出,所以首先打开输出流。

* 需要一个工具类FileUtil,与I/O相关(每一行代码去读取,是能够帮助数据转换成二进制进行传输 ---> 当前html代码是一个文件形式,在网络上传输文件形式非常麻烦。于是用输入输出的方式把文件转换成相应的二进制形式,通过二进制形式对数据进行传输。)

* 想要输出静态资源,少不了FileUtil的支持,调用它的getResourcePath()方法,把访问路径放进去。---->获取当前访问路径。

4.导入工具包

FileUtil

java 复制代码
package com.qcby.tomcat.util;

import java.io.*;

/**
 * 该类的主要作用是进行读取文件
 */
public class FileUtil {
    public  static  boolean witeFile(InputStream inputStream, OutputStream outputStream){
        boolean success = false ;
        BufferedInputStream bufferedInputStream ;
        BufferedOutputStream bufferedOutputStream;

        try {
            bufferedInputStream = new BufferedInputStream(inputStream);
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            bufferedOutputStream.write(ResponseUtil.responseHeader200.getBytes());

            int count = 0;
            while (count == 0){
                count = inputStream.available();
            }
            int fileSize = inputStream.available();
            long written = 0;
            int beteSize = 1024;
            byte[] bytes = new byte[beteSize];
            while (written < fileSize){
                if(written + beteSize > fileSize){
                    beteSize = (int)(fileSize - written);
                    bytes = new byte[beteSize];
                }
                bufferedInputStream.read(bytes);
                bufferedOutputStream.write(bytes);
                bufferedOutputStream.flush();
                written += beteSize;
            }
            success = true;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return success;
    }

    public static boolean writeFile(File file,OutputStream outputStream) throws Exception{
        return witeFile(new FileInputStream(file),outputStream);
    }

    /**
     * 获取资源地址
     * @param path
     * @return
     */
    public static String getResoucePath(String path){
        String resource = FileUtil.class.getResource("/").getPath();
        return resource + "\\" + path;
    }


}

ResponseUtil

java 复制代码
package com.qcby.tomcat.util;

public class ResponseUtil {
    public static  final String responseHeader200 ="HTTP/1.1 200 \r\n"+
            "Content-Type:text/html \r\n"+"\r\n";;

    public static String getResponseHeader200(String context){
        //响应头的添加
        return "HTTP/1.1 200 \r\n"+
                "Content-Type:text/html \r\n"+"\r\n" +context;
    }

}

这要解释一下,ResponseUtil为添加响应头

每个响应和请求是类似的,完整的相应包括响应头和响应内容

我们用这个工具包手动添加响应头

5.创建HttpServlet抽象类

用于定位到动态资源的请求方式。

java 复制代码
package com.qcby.tomcat.HttpServlet;

import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.Response.Response;

import java.io.IOException;

public abstract class HttpServlet {

    public  void service(Request request, Response response) throws IOException {
        if(request.getMethod().equals("GET")){
            doGet(request,response);
        }else if (request.getMethod().equals("POST")){
            doPost(request,response);
        }else if (request.getMethod().equals("")){
            System.out.println("未获取到方法类型");
        }
    }
    public abstract void doGet(Request request,Response response) throws IOException;

    public abstract void doPost(Request request,Response response)throws IOException;


}
6.创建Servlet容器
java 复制代码
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(){

    }
}

这串代码是对MyWeb包里的servlet进行管理。

这段代码是一个简单的Servlet配置映射工具,用于在自定义的Tomcat服务器中加载和映射Servlet。它扫描指定包下的所有类,查找带有@WebServlet注解的类,并将这些类的路径(URL路径)与类本身映射起来,存储在一个静态的HashMap中。

7.MyWeb放置动态资源

里面三个Servlet

java 复制代码
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.util.ResponseUtil;
import com.qcby.tomcat.webservlet.WebServlet;

import java.io.IOException;

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


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

        String context="<h1>你好我是FirstServlet</h1>";
        response.wirth(ResponseUtil.getResponseHeader200(context));

    }

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

    }
}

其余两个Servlet类似

8.MyTomcat核心类
java 复制代码
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 java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {
    public static Request request=new Request();
    public static Response 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();
        response=new Response(socket.getOutputStream());
        requestContext(inputStream);
    }
    public static void requestContext(InputStream inputStream) throws Exception {
        //将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 Exception{
        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请求
            }else { //静态资源
                response.wirthHtml(request.getPath());
            }
        }
    }

}

dis方法是处理http请求

在非空请求的基础下判断是动态资源还是静态资源

四、结果展示:

首先我们对动态资源------MyFirstServlet进行访问

接下来我们对静态资源------Demo.html进行访问

相关推荐
chuanauc5 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴21 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao27 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78731 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法
小阳拱白菜8 小时前
java异常学习
java