搭建Tomcat(四)---Servlet容器

目录

引入

Servlet容器

一、优化MyTomcat

①先将MyTomcat的main函数搬过来:

②将getClass()函数搬过来

③创建容器

④连接ServletConfigMapping和MyTomcat

连接:

⑤完整的ServletConfigMapping和MyTomcat方法:

a.ServletConfigMapping

b.MyTomcat

二、优化Server

方法1:调用(可以用,但是这里咱们不用,过于冗余)

[方法二: 直接嵌套](#方法二: 直接嵌套)

三、获取类对象

处理请求:

实现上述操作后,就可以测试是否正确:

[本地的Key值是 "/myFrist"](#本地的Key值是 “/myFrist”)

去客户端(浏览器)输入试试:

结果:


引入

在先前的tomcat搭建学习中,已经对tomcat的雏形做了基本的实现,即如下的过程:

接下来继续tomcat的搭建。

Servlet容器

目前Servlet容器:

接下来,我们来优化一下MyTomcat:

一、优化MyTomcat

首先在tomcat包下创建一下config包,然后在里面创建文件ServletConfigMapping:

那么ServletConfigMapping里面编写什么呢?就是从MyTomcat中编写的内容,现在需要copy到这个文件中,实现MyTomcat的解放:

【ServletConfigMapping在tomcat中就是为了获取配置信息;而MyTomcat中写的,正是扫描的过程,即未来扫描动态资源映射表的过程,所以现在将它放进合适的地方,即ServletConfigMapping里面】

注意: 不是原封不动的照搬,需要做一些小小的调整。

①先将MyTomcat的main函数搬过来:

注意这里做了个小调整,将MyTomcat的main函数里面的内容放进了代码块中;

static代码块的特点:在main函数执行之前先执行

static{
        try {
            // 1. 扫描包路径 (com.wzh.tomcat.myweb)
            String packageName = "com.qcby.tomcat.myweb";
            List<Class<?>> classes = getClasses(packageName);   //通过getClasses()方法获取到了myweb这个包下面的所有类的类对象,并将其放到了类对象中

            // 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.url());
                    classMap.put(webServlet.url(),(Class<HttpServlet>) clazz);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

②将getClass()函数搬过来

这里直接copy就好,放到刚才的static代码块之下:

java 复制代码
/**
     * 获取指定包下的所有类
     *
     * @param packageName 包名,例如 "com.qcby.tomcat.myweb"
     * @return 类对象列表
     * @throws Exception
     */
    //将MyTomcat中的getClass方法也粘贴过来
    private static List<Class<?>> getClasses(String packageName) throws Exception {
        List<Class<?>> classes = new ArrayList<>(); //将类文件封装进List中
        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")) {    //获得.class文件
                        // 获取类的完整类名
                        String className = packageName + "." + file.getName().replace(".class", "");
                        classes.add(Class.forName(className));
                    }
                }
            }
        }
        return classes;
    }

基于static代码块会在main方法之前执行的特性,想要执行static中的内容,只需要放一个空的main函数运行即可:

java 复制代码
public static void main(String[] args) {
        //当然也可以直接将static中的内容放进main函数里面(不常用)
  }

③创建容器

真正的servlet容器:

java 复制代码
public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();

/*
* 至于这里value的位置为什么写HttpServlet呢?按理说应该写类对象----多态
* 多态--父类的引用指向子类的对象
* 不妨来看一下,能写进来的MyFirstServlet、MySecondServlet、MyThirdServlet等的类对象
* 这些类对象都继承了HttpServlet
* 即:
* MyFirstServlet的类对象--->是HttpServlet的子类
* MySecondServlet的类对象--->是HttpServlet的子类
* MyThirdServlet的类对象--->是HttpServlet的子类
*【多态性--父类的引用指向子类的对象,并且子类的对象可以向上转型为父类的引用】
* */

④连接ServletConfigMapping和MyTomcat

我们只是想优化MyTomcat,而不是彻底换掉MyTomcat,所以现在我们要做的就是将创建的ServletConfigMapping和先前的MyTomcat连接起来:

当然由于主要代码已经放进ServletConfigMapping中了,所以原先的MyTomcat可以清空了。

连接:

注意②中的执行方法---放main;那么想要连接ServletConfigMapping和MyTomcat,我们依旧可以采用这种方法:让MyTomcat调用ServletConfigMapping中的方法,迫使ServletConfigMapping中的static代码块运行。

java 复制代码
public class MyTomcat {
    public static void main(String[] args) {
        ServletConfigMapping.init();
        //通过调用ServletConfigMapping中的静态方法来促使ServletConfigMapping类的执行,即将这两个类通过这种方式连接
    }
}

当然这个写在ServletConfigMapping中的方法,可以是空的。

⑤完整的ServletConfigMapping和MyTomcat方法:

a.ServletConfigMapping
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 {

    //真正的servlet容器
    /*
    * 至于这里value的位置为什么写HttpServlet呢?按理说应该写类对象----多态
    * 多态--父类的引用指向子类的对象
    * 不妨来看一下,能写进来的MyFirstServlet、MySecondServlet、MyThirdServlet等的类对象
    * 这些类对象都继承了HttpServlet
    * 即:
    * MyFirstServlet的类对象--->是HttpServlet的子类
    * MySecondServlet的类对象--->是HttpServlet的子类
    * MyThirdServlet的类对象--->是HttpServlet的子类
    *【多态性--父类的引用指向子类的对象,并且子类的对象可以向上转型为父类的引用】
    * */
    public static Map<String,Class<HttpServlet>> classMap=new HashMap<>();



    //扫描遍历(即MyTomcat之中的内容)
    //static代码块在main方法执行之前执行
    //将MyTomcat中的main方法放进代码块中
    static{
        try {
            // 1. 扫描包路径 (com.wzh.tomcat.myweb)
            String packageName = "com.qcby.tomcat.myweb";
            List<Class<?>> classes = getClasses(packageName);   //通过getClasses()方法获取到了myweb这个包下面的所有类的类对象,并将其放到了类对象中

            // 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.url());
                    classMap.put(webServlet.url(),(Class<HttpServlet>) clazz);

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取指定包下的所有类
     *
     * @param packageName 包名,例如 "com.qcby.tomcat.myweb"
     * @return 类对象列表
     * @throws Exception
     */
    //将MyTomcat中的getClass方法也粘贴过来
    private static List<Class<?>> getClasses(String packageName) throws Exception {
        List<Class<?>> classes = new ArrayList<>(); //将类文件封装进List中
        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")) {    //获得.class文件
                        // 获取类的完整类名
                        String className = packageName + "." + file.getName().replace(".class", "");
                        classes.add(Class.forName(className));
                    }
                }
            }
        }
        return classes;
    }

    //基于static代码块会在main方法之前执行的特性,想要执行static中的内容,只需要放一个空的main函数运行即可
    public static void main(String[] args) {
        //当然也可以直接将static中的内容放进main函数里面(不常用)
    }

    public static void init(){

    }

}
b.MyTomcat
java 复制代码
package com.qcby.tomcat;


import com.qcby.tomcat.config.ServletConfigMapping;

public class MyTomcat {
    public static void main(String[] args) {
        ServletConfigMapping.init();//通过调用ServletConfigMapping中的静态方法来促使ServletConfigMapping类的执行,即将这两个类通过这种方式连接

    }
}

二、优化Server

用同样的方法,首先把server的main函数解放掉:

【将main函数中的内容取出,并放进新创的serverInit函数中,删除原来的main函数即可。】

java 复制代码
//优化Server,取出main方法
    public static void serverInit() throws Exception {
        // 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();
        }
    }

方法1:调用(可以用,但是这里咱们不用,过于冗余)

接着用同样的操作,在MyTomcat中调用这新创建的方法:

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

import com.qcby.tomcat.config.ServletConfigMapping;
import com.qcby.tomcat.socket.Server;

public class MyTomcat {
    public static void main(String[] args) {
        try {
            //通过调用ServletConfigMapping中的静态方法来促使ServletConfigMapping类的执行,即将这两个类通过这种方式连接
            ServletConfigMapping.init();//初始化servlet容器
            Server.serverInit();//启动server服务
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

【看着代码变多了很多,实际上就加了一行,然后为了安全性,抛出了异常】

方法二: 直接嵌套

这里我们用另一种方法去优化Server,即将Server和MyTomcat连接起来:

即直接将方才做过修改的代码粘贴到MyTomcat中,并且在MyTomcat的main函数中调用Server的serverInit(即过去Server类的main函数)

如下是这一小阶段完成后的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 com.qcby.tomcat.socket.Server;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {
    //实例化Request
    public static Request request = new Request();
    public static void main(String[] args) throws Exception {
        ServletConfigMapping.init();
        serverInit();
    }

    //优化Server,取出main方法
    public static void serverInit() throws Exception {
        // 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 { //获取全部信息
        //将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 method = firstLine.split("\\s")[0];
            String path = firstLine.split("\\s")[1];
            System.out.println(method + " " + path);
            //任何请求都会被打到这个类中,随后就会被解析
            //将解析后的数据(method和path放进申请的static的Request实例中--再被运输给其他需要的地方)
            request.setMethod(method);
            request.setPath(path);
        }
    }
}

三、获取类对象

前面已经对MyTomcat做了优化,下一步就是去获取类对象;

如何获取类对象呢?简单来说谁存储着类对象的信息呢?---Request

那么就要接着对server类做出优化---因为目前Server和MyTomcat类还是存在问题,两者打不通;

既然想把两者联系起来,那么就要有嵌套调用或是直接嵌套。

在这里,我们依旧是用直接嵌套的方法写:

处理请求:

处理接收到的请求,首先在Map映射中寻找是否能够匹配上key值,即传送的path值

能查到则取获取Map的value值,即类对象:

java 复制代码
 //处理请求
    public static void dis(Request request) throws Exception {
        if(!request.getPath().equals("")){
            if(ServletConfigMapping.classMap.get(request.getPath())!=null){
                //能进到这里,则说明能够获取Map中对应的key值,能够匹配上
                Class<HttpServlet> classServlet=ServletConfigMapping.classMap.get(request.getPath());
                HttpServlet servlet=classServlet.newInstance(); //newInstance动态地创建一个类的新实例(对象)
                servlet.doGet(request,response);

            }
        }
    }

上面的代码最终是要被requestContext(InputStream inputStream)函数调用的。

即最终的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 com.qcby.tomcat.socket.Server;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {
    //实例化Request
    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 dis(Request request) throws Exception {
        if(!request.getPath().equals("")){
            if(ServletConfigMapping.classMap.get(request.getPath())!=null){
                //能进到这里,则说明能够获取Map中对应的key值,能够匹配上
                Class<HttpServlet> classServlet=ServletConfigMapping.classMap.get(request.getPath());
                HttpServlet servlet=classServlet.newInstance(); //newInstance动态地创建一个类的新实例(对象)
                servlet.doGet(request,response);

            }
        }
    }

    //优化Server,取出main方法
    public static void serverInit() throws Exception {
        // 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 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 method = firstLine.split("\\s")[0];
            String path = firstLine.split("\\s")[1];
            System.out.println(method + " " + path);
            //任何请求都会被打到这个类中,随后就会被解析
            //将解析后的数据(method和path放进申请的static的Request实例中--再被运输给其他需要的地方)
            request.setMethod(method);
            request.setPath(path);
        }
        //调用上面的处理函数
        dis(request);
    }
}

实现上述操作后,就可以测试是否正确:

本地的Key值是 "/myFrist"
去客户端(浏览器)输入试试:

输入对应key值的:

结果:

取看输出这里:
可以看出,上述代码被顺利执行了。

相关推荐
zeijiershuai3 分钟前
Java Arrays类、Comparable、Comparator
java·开发语言
goTsHgo12 分钟前
完整微服务设计 功能实现
java·微服务
ghostwritten13 分钟前
Linux setfacl 命令详解
linux·服务器·网络
hualinux14 分钟前
windwos defender实现白名单效果(除了指定应用或端口其它一律禁止)禁止服务器上网
运维·服务器·windwos防火墙·defender防火墙·win防火墙白名单·防火墙白名单效果
王家视频教程图书馆14 分钟前
请求go web后端接口 java安卓端播放视频
android·java·前端
是一只努力的小菜鸡啦14 分钟前
Zerotier + VSCode远程连接实验室的服务器、Xshell连接远程服务器
服务器·ide·vscode
安全狗新闻14 分钟前
服务器被入侵登录不上怎么办?
服务器
小沈同学呀15 分钟前
Java 本地缓存实现:Guava Cache、Caffeine、Ehcache 和 Spring Cache
java·缓存·guava·caffeine·ehcache
潜意识起点19 分钟前
Java游戏开发基础:从零开始制作一个简单的2D游戏
java·python·游戏
李三醒28 分钟前
Apache Tomcat 漏洞CVE-2024-50379条件竞争文件上传漏洞 servlet readonly spring boot 修复方式
spring boot·tomcat·apache