搭建Tomcat(三)---重写service方法

news2024/12/19 20:44:24

目录

引入

一、在Java中创建一个新的空项目(初步搭建)

问题:

要求在tomcat软件包下的MyTomcat类中编写main文件,实现在MyTomcat中扫描myweb软件包中的所有Java文件,并返回“@WebServlet(url="myFirst")”中url内填写的值:

①main函数解析:

首先,main函数用try-catch做了异常处理:

指定包名:

 获取包下所有类的类对象:

扫描遍历:

二、搭建服务器端

在socket软件包的Server文件中编写:当存在客户端连接后,获取:

分析一下获取路径和请求方法的过程:

三、搭建

重写service方法:


引入

前面已经提到了,TomCat就是项目运行的环境,之前用到的Servlet文件都是通过eclipse中的tomcat容器来运行的,那么接下来在Java文件中去模拟这个过程。

在tomcat项目中创建Servlet项目。

一、在Java中创建一个新的空项目(初步搭建)

创建新项目后再去在目录下创建如下软件包Java类。

@WebServlet接口中代码:

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;

@Retention(value= RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE})
public @interface WebServlet {
    String url() default "";
    String className() default "";
}

myweb里面的MyFirstServlet等三个文件内调用@WebServlet接口,如下代码:
(以MyFirstServlet为例:)

package com.qcby.tomcat.myweb;

import com.qcby.tomcat.HttpServlet.HttpServlet;
import com.qcby.tomcat.webservlet.WebServlet;

@WebServlet(url="myFirst")
public class MyFirstServlet extends HttpServlet {

}

问题:

要求在tomcat软件包下的MyTomcat类中编写main文件,实现在MyTomcat中扫描myweb软件包中的所有Java文件,并返回“@WebServlet(url="myFirst")”中url内填写的值:

MyTomcat内:

package com.qcby.tomcat;


import com.qcby.tomcat.webservlet.WebServlet;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class MyTomcat {
    public static void main(String[] args) {
        //扫描myweb这个包下的所有文件,并获取到它的@WebServlet中的url的值
        // get current dir
        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());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();//打印异常
        }
    }

    /**
     * 获取指定包下的所有类
     *
     * @param packageName 包名,例如 "com.qcby.tomcat.myweb"
     * @return 类对象列表
     * @throws Exception
     */
    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文件(.java->.class)此处获取的就是经过编译后的.class文件
                        // 获取类的完整类名
                        String className = packageName + "." + file.getName().replace(".class", "");
                        classes.add(Class.forName(className));
                    }
                }
            }
        }
        return classes;
    }
}

解析:

①main函数解析:
首先,main函数用try-catch做了异常处理:
  • try-catch块用于捕获并处理在扫描包和读取注解时可能发生的异常。
指定包名
  • String packageName = "com.qcby.tomcat.myweb";:定义了一个字符串变量packageName,它存储了要扫描的包名。
 获取包下所有类的类对象
  • List<Class<?>> classes = getClasses(packageName);
  • 这行代码调用了一个名为getClasses的方法(后面分析getClasses就知道这是一个获取类对象的方法),将这些获取的类对象写入到一个泛型中,方便后续遍历。
  • 调用getClasses方法,传入包名,获取该包下所有类的Class对象列表,并存储在classes变量中。
扫描遍历:

对获得并写入List中的每个文件进行isAnnotationPresent判断是否有@WebServlet注解:

注释:isAnnotationPresent是Java语言中的一种方法,它主要用于判断某个类、方法、变量等元素上是否存在指定类型的注解。】

  • 调用clazz.getAnnotation(WebServlet.class);时,Java虚拟机(JVM)会做以下几件事情:
  1. 检查clazz对象所代表的类上是否存在WebServlet注解的实例。
  2. 如果存在,返回这个注解的实例。
  3. 如果不存在,返回null

扫描获得后打印输出。 

那么分析完成main函数后,接着来分析一下getClases方法具体是怎么实现获取包中类信息的:

(详细参见注释:)

// 定义一个静态方法,用于获取指定包名下的所有类对象列表
private static List<Class<?>> getClasses(String packageName) throws Exception {
    List<Class<?>> classes = new ArrayList<>(); // 初始化一个ArrayList,用于存储找到的类对象

    // 将包名中的点(.)替换为文件路径中的斜杠(/),以构造资源路径
    String path = packageName.replace('.', '/');

    // 获取当前线程的类加载器,用于加载资源
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    
    // 通过类加载器获取指定路径下的所有资源(可能是目录或JAR文件中的条目)
    Enumeration<URL> resources = classLoader.getResources(path);

    // 遍历所有找到的资源
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement(); // 获取下一个资源URL

        // 注意:这里假设资源是一个文件系统上的目录,但这不是总是正确的,特别是当资源在JAR中时
        File directory = new File(resource.toURI()); // 将资源URL转换为File对象(这里存在潜在问题,对于JAR文件不适用)

        // 检查目录是否存在(对于文件系统上的资源有效)
        if (directory.exists()) {
            // 遍历目录下的所有文件和子目录
            for (File file : directory.listFiles()) {
                // 检查文件是否以".class"结尾,即是否是类文件(.java文件经过编译后的.class文件)
                if (file.getName().endsWith(".class")) {
                    // 构造类的完整名称(包括包名)
                    String className = packageName + "." + file.getName().replace(".class", "");
                    
                    // 使用反射加载类,并添加到类列表中
                    // 注意:这里可能会抛出ClassNotFoundException,但在这个方法内部没有捕获处理
                    classes.add(Class.forName(className));
                }
            }
        }
    }
    // 返回找到的类对象列表
    return classes;
}

二、搭建服务器端

在socket软件包的Server文件中编写:
当存在客户端连接后,获取:

package com.qcby.tomcat.socket;


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

public class Server {
    public static void main(String[] args) 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 path=firstLine.split("\\s")[1];
            String method=firstLine.split("\\s")[0];
            System.out.println(path+" "+method);
        }
    }

//获取第一个词和第二个词
//    public static void requestContext(InputStream inputStream) throws IOException {
//        StringBuilder sb = new StringBuilder();
//        int ch;
//        // 读取输入流直到遇到换行符或文件结束
//        while ((ch = inputStream.read()) != -1) {
//            if (ch == '\n') {
//                break; // 遇到换行符,停止读取
//            }
//            sb.append((char) ch); // 将读取的字符添加到StringBuilder中
//        }
//
//        String firstLine = sb.toString().trim(); // 获取第一行并去除首尾空格
//        if (firstLine.isEmpty()) {
//            System.out.println("你输入了一个空请求");
//        } else {
//            String[] words = firstLine.split("\\s+"); // 使用正则表达式按空格分割单词
//            if (words.length >= 2) {
//                System.out.println("第一个词是:" + words[0]);
//                System.out.println("第二个词是:" + words[1].substring(1));
//            } else {
//                System.out.println("第一行没有足够的词");
//            }
//        }
//    }



}

分析一下获取路径和请求方法的过程:

  1. String firstLine = Context.split("\\n")[0];
    • 这行代码将 Context 字符串按照换行符(\n)进行分割,并取出分割后的第一个元素(即第一行)赋值给 firstLine
  2. String path = firstLine.split("\\s")[1];
    • 接着,这行代码将 firstLine 按照空白字符(包括空格、制表符等,\s 是一个正则表达式,用于匹配任何空白字符)进行分割,并取出分割后的第二个元素(索引为1)赋值给 path。这里假设路径是 firstLine 中方法名后面的第一个元素。
  3. String method = firstLine.split("\\s")[0];
    • 这行代码再次对 firstLine 按照空白字符进行分割,并取出分割后的第一个元素(索引为0)赋值给 method。这里假设方法名是 firstLine 中的第一个元素。
  4. System.out.println(path + " " + method);
    • 最后,这行代码打印出 path 和 method 的值,但顺序是先打印 path,后打印 method,中间用空格分隔。

三、搭建

【注:接口--接口就是用来定义方法的;实现接口类就必须实现接口中的方法;

抽象类中可以有抽象方法,也可以有具体方法;抽象类实现了接口,那么抽象类可以有选择性的实现接口中的方法】

重写service方法:

HTTP请求:

请求方法--Get,Post....等(主要是Get\Post方式)

而上述实现service方法,就是为了获取请求方法并且判断是Get还是Post请求(然后将请求送到doGet()/doPost()方法中)

在HttpServlet包中的方法HttpServlet抽象类中重写service方法:

首先创建request:

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

    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;
    }
}

并且修改Server来得到path和method:

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 {
    //实例化Request
    private static Request request = new Request();

    public static void main(String[] args) 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);
        }
    }

即可通过这种方式让HttpServlet中的service方法得到Http请求的key和请求方法:

import com.qcby.tomcat.Request.Request;
import com.qcby.tomcat.socket.Server;

public abstract class HttpServlet {

    /*
    * 一定不能是抽象方法因为HttpServlet是要被实现的
    * 根据用户的请求,来调用不用的方法去处理
    *       GET请求   doGet()请求
    *       POST请求  doPost()请求
    *
    * */
    //重写service()方法
    public  void service(Request request){//一定不能是抽象方法因为HttpServlet是要被实现的
        if(request.getMethod().equals("GET")){
                doGet(request);
        }else if(request.getMethod().equals("POST")){
                doPost(request);
        }
    }

    //去实现doGet--意味着所有继承HttpServlet抽象类的对象,都要去实现这两个方法
    public abstract void doGet(Request request);

    //去实现doPost
    public abstract void doPost(Request request);


}

那么就意味着(继承抽象类的实例必须实现抽象类中的方法):

那么到这里,一个基础的、连贯的tomcat雏形就存在了:

四、Tomcat雏形

1.tomcat的server接收到一个请求:

发送请求:

2.被tomcat的server接收到:

 3.此时server内部创建了一个static的Request实例,并被HttpServlet里面的service接收:

通过if-else if判断后,被送到对应的doGet或doPost方法:

由于HttpServlet是抽象类,所以所有继承这个抽象类的实例都要实现抽象类中的所有方法: 

类似:

同时,由于doGet()和doPost()方法都是抽象方法,所以HttpServlet想要实现这些方法,就要去到各自的实例中,而这个实例究竟是哪一个,则就对应path中存的路径了。 

以上就是一个基础的雏形(关于tomcat是如何接收并初步处理一个请求的)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2262354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CAN配置---波特率中断引脚等---autochips-AC7811-ARM-M3内核

1、配置工具 虽然不怎么好用&#xff0c;但比没有强多了。具体看图&#xff1a; 时钟选着 NVIC配置 GPIO配置 2、生成的具体配置信息 NXP的配置工具里面&#xff0c;具体的波特率可以直接显示&#xff0c;这个工具没有&#xff0c;怎么办&#xff1f; 它放到了生成的代码里面…

matlab Patten的使用(重要)(Matlab处理字符串一)

原文连接&#xff1a;https://www.mathworks.com/help/releases/R2022b/matlab/ref/pattern.html?browserF1help 能使用的搜索函数&#xff1a; contains确定字符串中是否有模式matches确定模式是否与字符串匹配count计算字符串中模式的出现次数endsWith确定字符串是否以模式…

Docker创建一个mongodb实例,并用springboot连接 mongodb进行读写文件

一、通过Docker 进行运行一个 mongodb实例 1、拉取镜像 docker pull mongo:5.0.5 2、创建 mongodb容器实例 docker run -d --name mongodb2 \-e MONGO_INITDB_ROOT_USERNAMEsalaryMongo \-e MONGO_INITDB_ROOT_PASSWORD123456 \-p 27017:27017 \mongo:5.0.5 3、进入容器&am…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍02-基于错误消息的SQL注入(Error-Based SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

OpenCVE:一款自动收集NVD、MITRE等多源知名漏洞库的开源工具,累计收录CVE 27万+

漏洞库在企业中扮演着至关重要的角色&#xff0c;不仅提升了企业的安全防护能力&#xff0c;还支持了安全决策、合规性要求的满足以及智能化管理的发展。前期博文《业界十大知名权威安全漏洞库介绍》介绍了主流漏洞库&#xff0c;今天给大家介绍一款集成了多款漏洞库的开源漏洞…

Spring Boot 3.X:Unable to connect to Redis错误记录

一.背景 最近在搭建一个新项目&#xff0c;本着有新用新的原则&#xff0c;项目选择到了jdk17SpringBoot3.4。但是在测试Redis连接的时候却遇到了以下问题&#xff1a; redis连不上了。于是我先去检查了配置文件的连接信息&#xff0c;发现没问题&#xff1b;再去检查配置类&am…

MinT: 第一个能够生成顺序事件并控制其时间戳的文本转视频模型。

MinT 是第一个能够生成顺序事件并控制其时间戳的文本转视频模型。使用 MinT 生成时间控制的多事件视频。给定一系列事件文本提示及其所需的开始和结束时间戳&#xff0c;MinT 可以合成具有一致主题和背景的平滑连接事件。此外&#xff0c;它可以灵活地控制每个事件的时间跨度。…

C语言实验 结构体2

时间:2024.12.18 6-5 评委打分-t-CalcuScore 代码 // 定义结构体 struct Score {int id;char name[10];int value[17];double finalScore;int rank; };// 计算最终成绩 void CalcuScore(struct Score grade[], int n) {for (int i = 0; i < n; i++) {int max = grade[i].…

第6章 第一组重构

最常用到的重构就是用提炼函数&#xff08;106&#xff09;将代码提炼到函数中&#xff0c;或者用提炼变量&#xff08;119&#xff09;来提炼变量。既然重构的作用就是应对变化&#xff0c;你应该不会感到惊讶&#xff0c;我也经常使用这两个重构的反向重构——内联函数&#…

基于python对网页进行爬虫简单教程

python对网页进行爬虫 基于BeautifulSoup的爬虫—源码 """ 基于BeautifulSoup的爬虫### 一、BeautifulSoup简介1. Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱&#xff0c;通过解析文档为用户提供需要…

C语言中文件是什么?文件文本和二进制文件的区别

1、C语言中文件是什么&#xff1f; 我们对文件的概念已经非常熟悉了&#xff0c;比如常见的 Word 文档、txt 文件、源文件等。文件是数据源的一种&#xff0c;最主要的作用是保存数据。 在操作系统中&#xff0c;为了统一对各种硬件的操作&#xff0c;简化接口&#xff0c;不同…

vmware workstation pro上创建虚拟机

vmware workstation pro上创建虚拟机 下载vmware workstation pro软件安装后并运行点击主页&#xff0c;选择创建虚拟机 创建虚拟机成功后会出现如下界面 可以点击设置按钮删除不需要的硬件&#xff0c;也可以添加新的硬件设备&#xff0c;最终硬件信息如下图 至此虚拟机…

【数学建模】利用Matlab绘图(2)

一、Matlab中plot函数的基本用法 在matlab中&#xff0c;函数的基本用法主要包括以下几种 第一类&#xff1a; plot(X,Y,LineSpec) 第二类&#xff1a; plot(tbl,xvar,yvar) 1.1 第一类 1.1.1x-y坐标 x和y的选择取决于绘图所需的数据类型以及图像的类型。下表列出了几种…

ASP.NET Core - 依赖注入 自动批量注入

依赖注入配置变形 随着业务的增长&#xff0c;我们项目工作中的类型、服务越来越多&#xff0c;而每一个服务的依赖注入关系都需要在入口文件通过Service.Add{}方法去进行注册&#xff0c;这将是非常麻烦的&#xff0c;入口文件需要频繁改动&#xff0c;而且代码组织管理也会变…

Oracle 适配 OpenGauss 数据库差异语法汇总

背景 国产化进程中&#xff0c;需要将某项目的数据库从 Oracle 转为 OpenGauss &#xff0c;项目初期也是规划了适配不同数据库的&#xff0c;MyBatis 配置加载路径设计的是根据数据库类型加载指定文件夹的 xml 文件。 后面由于固定了数据库类型为 Oracle 后&#xff0c;只写…

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)

Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版) 文章目录 Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)一.环境准备1.服务器准备2.环境配置3.设置主机名4.修改国内镜像源地址5.配置时间同步6.配置内核转发及网桥过滤二.容器运行时Containerd安装(所有节点)…

[LeetCode-Python版]21. 合并两个有序链表(迭代+递归两种解法)

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#x…

MATLAB引用矩阵元素的几种方法

引用矩阵元素可以通过索引&#xff0c;也可以通过逻辑值 索引 通过引用元素在矩阵中的位置来提取元素&#xff0c;例如&#xff1a; - 逻辑值 通过某种逻辑运算来使得要提取的值变为逻辑 1 1 1&#xff0c;用 A ( ) A() A()提取即可&#xff0c; A A A为原矩阵的名称。 例如&…

sql 批量修改字段 的默认值

SELECT COLUMN_NAME, NUMERIC_PRECISION, NUMERIC_SCALE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA financeproject AND TABLE_NAME finance_balance AND DATA_TYPE decimal; 查出的字段 excel 拼接 修改语句 ALTER TABLE finance_income MODIFY COLUMN yy…

CVE-2023-0562【春秋云镜】

目录 CVE-2023-0562漏洞概述漏洞利用方式影响范围修复建议安全编码示例靶标介绍 CVE-2023-0562 CVE-2023-0562 是一个针对银行储物柜管理系统的SQL注入漏洞。该漏洞影响了储物柜管理系统中处理用户输入的部分&#xff0c;攻击者可以利用此漏洞未经授权地访问数据库中的敏感信息…