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进行访问

相关推荐
南山十一少6 分钟前
Spring Security+JWT+Redis实现项目级前后端分离认证授权
java·spring·bootstrap
427724002 小时前
IDEA使用git不提示账号密码登录,而是输入token问题解决
java·git·intellij-idea
chengooooooo2 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
李长渊哦2 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
计算机小白一个2 小时前
蓝桥杯 Java B 组之设计 LRU 缓存
java·算法·蓝桥杯
南宫生5 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长5 小时前
Maven 基础环境搭建与配置(一)
java·maven
风与沙的较量丶6 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
m0_748251726 小时前
SpringBoot3 升级介绍
java
极客先躯8 小时前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分