JAVAWeb08-手动实现 Tomcat 底层机制+ 自己设计 Servlet

news2024/12/23 1:21:23

1. 前言

先看一个小案例, 引出对 Tomcat 底层实现思考

1.1 完成小案例

在这里插入图片描述
在这里插入图片描述
● 快速给小伙伴完成这个小案例
0. 我们准备使用 Maven 来创建一个 WEB 项目, 老师先简单给小伙伴介绍一下 Maven 是什么, 更加详细的使用,我们还会细讲, 现在先使用一把
在这里插入图片描述
在这里插入图片描述

  1. 先创建一个 Maven 的 Web 项目 hsp-tomcat
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

补充:如何配置阿里 maven 镜像
(1) 把 maven的安装目录\conf\settings.xml 拷贝默认的 maven 配置目录
(2) C:\Users\Administrator.m2 目录 settings.xml
(3) 修改 C:\Users\Administrator.m2\settings.xml , 增加如下的部分

<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The
repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror.
IDs are used
| for inheritance and direct lookup purposes, and must be unique across the
set of mirrors. |
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
	<mirror>
		<id>alimaven</id>
		<name>aliyun maven</name>
		<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
		<mirrorOf>central</mirrorOf>
	</mirror>
</mirrors>

(4) 到此 ok

  1. 修改 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<!--解读
		1. dependency 表示依赖, 也就是我们这个项目需要依赖的 jar 包
		2. groupId 和 artifactId 被统称为坐标, 是为了去定位这个项目/jar
		3. groupId: 一般是公司 比如 com.baidu , 这里是 avax.servlet
		4. artifactId 一般是项目名, 这里是 javax.servlet-api
		5. 这样的化就可以定位一个 jar 包
		6. version 表示你引入到我们项目的 jar 包的版本是 3.1.0
		7. scope: 表示作用域,也就是你引入的 jar 包的作用范围
		8. provided 表示在 tomcat 本身是有这个 jar 的,因此在编译,测试使用,但是在打包
		发布就不用要带上
		9. 在默认情况下, 引入的 jar 会到 中央仓库去下载 https://mvnrepository.com/
		10. 会下载到哪里到你指定的目录 C:\Users\Administrator\.m2\repository
		11. 有时为了下载更快, 往往配置镜像,
		12. 在 默 认 的 路 径 下 拷 贝 一 份 setting.xml 到
		C:\Users\Administrator\.m2\settings.xml
		13. 指定默认的阿里云镜像
			<mirrors>
			<mirror>
			<id>alimaven</id>
			<name>aliyun maven</name>
			<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
			<mirrorOf>central</mirrorOf>
			</mirror>
			</mirrors>
		-->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>
  1. 创建 cal.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
<h1>计算器</h1>
<form action="/calServlet" method="get">
    num1:<input type="text" name="num1"><br/>
    num2:<input type="text" name="num2"><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

  1. 创建 java 目录,存放 java 源文件在这里插入图片描述
  2. 创建 CalServlet.java
    在这里插入图片描述
  3. 修改 web.xml , 配置 Servlet
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
	<display-name>Archetype Created Web Application</display-name>
	<servlet>
		<servlet-name>CalServlet</servlet-name>
		<servlet-class>com.hspedu.servlet.CalServlet</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>CalServlet</servlet-name>
		<url-pattern>/calServlet</url-pattern>
	</servlet-mapping>
</web-app>
  1. 修改 CalServlet.java, 完成计算任务
public class CalServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {
        //接收提交的数据进行计算
        //复制当前行 ctrl+alt+下光标
        String strNum1 = request.getParameter("num1");
        String strNum2 = request.getParameter("num2");

        //把strNum1 和 strNum2 转成 int
        int num1 = WebUtils.parseInt(strNum1, 0);
        int num2 = WebUtils.parseInt(strNum2, 0);
        int result = num1 + num2;

        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print("<h1>" + num1 + " + " + num2 + " = " + result + "<h1>");
        writer.flush();
        writer.close();

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }
}
  1. 创 建 工 具 类 WebUtils.java
public class WebUtils {

    /**
     * 将一个字符串数字,转成 int, 如果转换失败,就返回传入 defaultVal
     * @param strNum
     * @param defaultVal
     * @return
     */
    public static int parseInt(String strNum, int defaultVal) {

        try {
            return Integer.parseInt(strNum);
        } catch (NumberFormatException e) {
            System.out.println(strNum + " 格式不对,转换失败");
        }

        return defaultVal;
    }


}
  1. 配置 tomcat
    略。。。
  2. 启动 tomcat
  3. 浏览器访问: http://localhost:8080/cal.html
    在这里插入图片描述
  4. 完成测试

maven 中央仓库 : https://mvnrepository.com/

1.2 思考

问题: Tomcat 底层实现 和 调用到 Servlet 流程?

我们的目标: 不用 Tomcat, 不用系统提供的 Servlet, 模拟 Tomcat 底层实现并能调用我们自己设计的 Servle, 也能完成相同的功能

1.3 Tomcat 整体架构分析

1.3.1 一图胜千言

● 说明: Tomcat 有三种运行模式(BIO, NIO, APR), 因为老师核心讲解的是 Tomcat 如何接收客户端请求,解析请求, 调用 Servlet , 并返回结果的机制流程, 采用 BIO 线程模型来模拟.[绘图]
在这里插入图片描述

2. 手动实现 Tomcat 底层机制+ 自己设计 Servlet

2.1 实现任务阶段 1

编写自己 Tomcat, 能给浏览器返回 Hi, Hspedu

基于 socket 开发服务端-流程:
在这里插入图片描述

2.1.1 需求分析/图解

  1. 需求分析如图, 浏览器请求 http://localhost:8080/??, 服务端返回 hi , hspedu
    在这里插入图片描述

2.1.2 分析+代码实现

● 分析示意图
在这里插入图片描述
● 代码实现

  1. 创 建 TomcatV1.java
/**
 * 这是第一个版本的tomcat ,可以完成,接收浏览器的请求,并返回信息
 */
public class HspTomcatV1 {
    public static void main(String[] args) throws IOException {

        //1. 创建ServerSocket, 在 8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======mytomcat在8080端口监听======");
        while (!serverSocket.isClosed()) {

            //等待浏览器/客户端的连接
            //如果有连接来,就创建一个socket
            //这个socket就是服务端和浏览器端的连接/通道
            Socket socket = serverSocket.accept();

            //先接收浏览器发送的数据
            //inputStream 是字节流=> BufferedReader(字符流)
            //java基础 IO , 第19章
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            String mes = null;
            System.out.println("=======接收到浏览器发送的数据=======");
            //循环的读取
            while ((mes = bufferedReader.readLine()) != null) {
                //判断mes的长度是否为0:判断字符串长度是否为0的作用是为了处理HTTP请求头和请求体之间的空行。
                if (mes.length() == 0) { 
                    break;//退出while
                }
                System.out.println(mes);
            }

            //我们的tomcat会送-http响应方式
            OutputStream outputStream = socket.getOutputStream();
            //构建一个http响应的头
            //\r\n 表示换行
            //http响应体,需要前面有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "hi, hspedu 韩顺平教育";

            System.out.println("========我们的tomcat 给浏览器会送的数据======");
            System.out.println(resp);

            outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回

            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();
            
        }
    }
}

细节说明:
1.判断字符串长度是否为0的作用是为了处理HTTP请求头和请求体之间的空行。
在HTTP请求中,请求头和请求体之间必须要有一个空行,表示请求头已经结束,请求体开始。因此,在读取HTTP请求时,需要先读取请求头并解析出请求信息,然后再读取请求体部分。而读取请求头时,每个请求头字段都是以冒号和空格分隔的键值对形式出现的,如果读取到的一行请求头字段的长度为0,就表明请求头已经结束了。此时就可以退出循环,继续处理请求体的内容了。
因此,为了在读取HTTP请求时正确地处理请求头和请求体之间的空行,需要添加判断字符串长度是否为0的逻辑。
 
2.在HTTP协议中,每行信息都以"\r\n"(回车+换行)作为结束符,表示该行信息结束,并让浏览器接收下一个信息。
所以在构造HTTP响应头部时,需要在每行信息后添加"\r\n"。而在Java中,“\n"表示换行,即使只写”\n"在大多数情况下也能正常工作,但是使用"\r\n"会更加规范和严谨,因为在Windows系统中,只用"\n"可能不会被认为是换行符,而使用"\r\n"则能保证适用于所有操作系统。
因此,这段代码中,在回复客户端的HTTP响应头中使用了"\r\n"作为结束符号,确保HTTP响应头的格式符合HTTP协议的规范。

  1. 测试 浏览器 : http://localhost:8080/
    在这里插入图片描述

2.1.3 问题分析

没有使用 BIO 线程模型,没有实现多线程,性能差

2.2 实现任务阶段 2

使用 BIO 线程模型,支持多线程

2.2.1 BIO 线程模型介绍

在这里插入图片描述

补充:
1.BIO是阻塞式I/O(Blocking I/O)的缩写,是Java网络编程中常用的一种I/O模型。它指的是在进行网络数据读写时,当用户线程发起了一个I/O操作(如读取数据),该线程会一直等待直到操作完成并返回结果,期间会一直阻塞在那里,无法做其他事情。与之相对的是非阻塞式I/O(NIO)和异步I/O(AIO)两种模型。
BIO模型是传统的线程池模型,即一个请求对应一个线程。在BIO模型中,一个客户端请求到来时,服务器会开启一个新的线程去处理这个请求,直到整个请求处理完毕才会退出线程。这种模型实现简单,易于理解,但同时也会带来一些问题,例如线程池大小的限制、线程上下文切换等问题。当线程池达到最大线程数时,新的请求就会被拒绝或者阻塞,降低系统的吞吐量和性能。
因此,在高并发、大规模的网络编程场景中,BIO不再适用,而需要使用NIO和AIO等更加高效的I/O模型。
 
2.NIO是Java NIO(New I/O)的缩写,指的是Java提供的一种高效的I/O处理方式。与传统的I/O操作(如BIO)不同,NIO是基于事件驱动的模型,可以在单线程中处理多个并发请求,提升了性能和可扩展性。
通过使用NIO库,Java应用程序可以实现非阻塞式I/O操作,包括文件操作、套接字操作等。它的主要优势在于使用少量线程就能处理大量并发连接,因此适用于高负载、高可靠性的网络应用程序。

2.2.2 需求分析/图解

  1. 需求分析如图, 浏览器请求 http://localhost:8080, 服务端返回 hi , hspedu, 后台hsptomcat 使用 BIO 线程模型,支持多线程=> 对前面的开发模式进行改造
    在这里插入图片描述

2.2.3 分析+代码实现

● 分析示意图
在这里插入图片描述
● 代码实现

HspTomcatV2.java

public class HspTomcatV2 {
    public static void main(String[] args) throws IOException {
        //在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=======hsptomcatV2 在8080监听=======");
        //只要 serverSocket没有关闭,就一直等待浏览器/客户端的连接
        while (!serverSocket.isClosed()) {
            //1. 接收到浏览器的连接后,如果成功,就会得到socket
            //2. 这个socket 就是 服务器和 浏览器的数据通道
            Socket socket = serverSocket.accept();
            //3. 创建一个线程对象,并且把socket给该线程
            //  这个是java线程基础
            HspRequestHandler hspRequestHandler =
                    new HspRequestHandler(socket);
            new Thread(hspRequestHandler).start();

        }
    }
}

HspRequestHandler.java

/**
 * 解读
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            InputStream inputStream = socket.getInputStream();

            // 把inputStream -> BufferedReader -> 方便进行按行读取
            BufferedReader bufferedReader =
            new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            // 不同的线程在和浏览器和客户端交互
            System.out.println("当前线程= " + Thread.currentThread().getName());

            System.out.println("=========hsptomcatv2 接收到的数据如下=========");
            String mes = null;
            
            
            while ((mes = bufferedReader.readLine()) != null) {
            //     如果长度为0 ""
                 if (mes.length() == 0) {
                     break; //退出
                 }
                 System.out.println(mes);
             }


            // 构建一下http响应头
            // 返回的http的响应体和响应头之间有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "<h1>hello world!</h1>";
            System.out.println("========hsptomcatv2返回的数据是=========");
			
            System.out.println(resp);
            
            // 返回数据给我们的浏览器/客户端-> 封装成http响应
            OutputStream outputStream = socket.getOutputStream();
            resp.getBytes() 是把字符串转成字节数组
            outputStream.write(resp.getBytes());
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭,
            //否则如果连接数过多会造成客户端等待
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
  • 测试:
    浏览器 :http://localhost:8080/
    在这里插入图片描述

2.2.4 问题分析

HspTomcat 只是简单返回结果,没有和 Servlet、web.xml 关联

2.3 实现任务阶段 3

处理 Servlet

2.3.1 Servlet 生命周期-回顾

在这里插入图片描述

2.3.2 需求分析/图解

● 需求分析如图, 浏览器请求 http://localhost:8080/hspCalServlet, 提交数据,完成计算任务,如果 servlet 不存在,返回 404
在这里插入图片描述
在这里插入图片描述

2.3.3 分析+代码实现

● 分析示意图
在这里插入图片描述
在这里插入图片描述
架构图:
在这里插入图片描述

2.3.3.1 优化1阶段

1.搭建结构
2.封装HttpServletRequest

1.模拟servlet
HspServlet.java

/**
 * 先搭建结构,后面在写内容
 */
public interface HspServlet {

}

HspHttpServlet.java

public abstract class HspHttpServlet implements HspServlet {
	
}

HspCalServlet.java

public class HspCalServlet extends HspHttpServlet {
   
}

2.模拟HttpServletRequest和HttpServletResponse
HspRequest.java

/**
 * 解读
 * 1. HspRequest 作用是封装http请求的数据
 * get /hspCalServlet?num1=10&num2=30
 * 2. 比如 method(get) 、 uri(/hspCalServlet) 、 还有参数列表 (num1=10&num2=30)
 * 3. HspRequest 作用就等价原生的servlet 中的HttpServletRequest
 * 4. 这里考虑的是GET请求
 */
public class HspRequest {

    private String method;
    private String uri;
    //存放参数列表 参数名-参数值 => HashMap
    private HashMap<String, String> parametersMapping =
            new HashMap<>();
    private InputStream inputStream = null;


    //构造器=> 对http请求进行封装 => 可以将老师写的代码封装成方法
    //inputStream 是和 对应http请求的socket关联
    public HspRequest(InputStream inputStream) {
        this.inputStream = inputStream;
        //完成对http请求数据的封装..
        encapHttpRequest();
    }

    /**
     * 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
     */
    private void encapHttpRequest() {
        System.out.println("HspRequest init()");
        try {
            //inputstream -> BufferedReader
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //读取第一行
            /**
             * GET /hspCalServlet?num1=10&num2=30 HTTP/1.1
             * Host: localhost:8080
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Fi
             */
            String requestLine = bufferedReader.readLine();
            //GET - /hspCalServlet?num1=10&num2=30 - HTTP/1.1
            String[] requestLineArr = requestLine.split(" ");
            //得到method
            method = requestLineArr[0];
            //解析得到 /hspCalServlet
            //1. 先看看uri 有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) { //说明没有参数列表
                uri = requestLineArr[1];
            } else {
                //[0,index)
                uri = requestLineArr[1].substring(0, index);
                //获取参数列表->parametersMapping
                //parameters => num1=10&num2=30
                String parameters = requestLineArr[1].substring(index + 1);
                //num1=10 , num2=30 .... parametersPair= ["num1=10","num2=30" ]
                String[] parametersPair = parameters.split("&");
                //防止用户提交时 /hspCalServlet?
                if (null != parametersPair && !"".equals(parametersPair)) {
                    //再次分割 parameterPair = num1=10
                    for (String parameterPair : parametersPair) {
                        //parameterVal ["num1", "10"]
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2) {
                            //放入到 parametersMapping
                            parametersMapping.put(parameterVal[0], parameterVal[1]);
                        }
                    }
                }
            }
            //这里不能关闭流 inputStream 和 socket关联
            //inputStream.close();

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

    //request对象有一个特别重要方法
    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)) {
            return parametersMapping.get(name);
        } else {
            return "";
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    @Override
    public String toString() {
        return "HspRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMapping=" + parametersMapping +
                '}';
    }
}

细节补充:
在Java中,Socket和流(Stream)通常是一起使用的,用于进行网络数据传输。如果关闭与Socket相关联的流会导致Socket也关闭的问题,取决于关闭流的方式。
如果使用Socket对象的close()方法关闭与其关联的输入流或输出流,则会同时关闭该Socket,因为这些流是通过Socket来创建的。例如:

Socket socket = new Socket("example.com", 80);
OutputStream os = socket.getOutputStream();
os.close(); // 关闭输出流,同时也会关闭socket

HspResponse

/**
 * 老师解读
 * 1. HspResponse对象可以封装OutputStream(是socket关联)
 * 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端
 * 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse
 */
public class HspResponse {

}

3.改进HspRequestHandler

/**
 * 解读
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            //这里我们先死后活
            HspRequest hspRequest = new HspRequest(socket.getInputStream());
            String num1 = hspRequest.getParameter("num1");
            String num2 = hspRequest.getParameter("num2");
            System.out.println("请求的参数num1= " + num1);
            System.out.println("请求的参数num2= " + num2);
            System.out.println("hspRequest= " + hspRequest);


            // 构建一下http响应头
            // 返回的http的响应体和响应头之间有两个换行 \r\n\r\n
            String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp = respHeader + "<h1>hello world!</h1>";
            System.out.println("========hsptomcatv2返回的数据是=========");
			
            System.out.println(resp);
            
            
            OutputStream outputStream = socket.getOutputStream();
            resp.getBytes() 是把字符串转成字节数组
            outputStream.write(resp.getBytes());
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭,
            //否则如果连接数过多会造成客户端等待
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.3.3.2 优化2阶段

1.完善HspResponse
2.构建Servlet模型
3.创建Util工具类
4.优化HspRequestHandler处理器

补充:HspResponse.java

/**
 * 老师解读
 * 1. HspResponse对象可以封装OutputStream(是socket关联)
 * 2. 即可以通过 HspResponse对象 返回Http响应给浏览器/客户端
 * 3. HspResponse对象 的作用等价于原生的servlet的 HttpServletResponse
 */
public class HspResponse {

    private OutputStream outputStream = null;

    //写一个http的响应头 => 先死后活
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    //说明同学们如果有兴趣, 在编写更多的方法
    //比如 setContentType

    //在创建 HspResponse 对象时,传入的outputStream是和Socket关联的
    public HspResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    //当我们需要给浏览器返回数据时,可以通过HspResponse 的输出流完成
    //
    public OutputStream getOutputStream() {
        return outputStream;
    }
}

完善模拟的Servlet
1.补充Servlet的生命周期

/**
 * 先搭建结构,后面在写内容
 * 老韩只保留了 三个核心方法声明
 */
public interface HspServlet {

    void init() throws Exception;

    void service(HspRequest request, HspResponse response) throws IOException;

    void destroy();
}

2.完善HttpServlet

public abstract class HspHttpServlet implements HspServlet {

    @Override
    public void service(HspRequest request, HspResponse response) throws IOException {
        //老师说明 equalsIgnoreCase 比较字符串内容是相同,不区别大小写
        if("GET".equalsIgnoreCase(request.getMethod())) {
            //这里会有动态绑定
            this.doGet(request,response);
        } else if("POST".equalsIgnoreCase(request.getMethod())) {
            this.doPost(request,response);
        }
    }

    //这里我们使用的了模板设计模式 => java 基础的 抽象类专门讲过模板设计模式
    //让HspHttpServlet 子类 HspCalServlet 实现

    public abstract void doGet(HspRequest request, HspResponse response);
    public abstract void doPost(HspRequest request, HspResponse response);
}

细节补充:
在这段代码中,this关键字和省略this关键字是没有区别的。因为在Java中,当局部变量和实例变量同名时,编译器会优先使用局部变量,如果需要访问实例变量,可以使用this关键字来代表当前对象。
 
因此,使用this.doGet()和直接调用doGet()方法实际上是等价的,不会影响程序的执行结果。只要doGet()方法已经定义在了当前类中,都可以直接调用。
 
不过,在实际开发中,推荐使用this关键字来调用当前对象的方法或访问成员变量,可以提高代码的可读性和可维护性。另外,使用this关键字还有助于避免和局部变量或参数命名冲突的情况。
 
因此,虽然this.doGet()和doGet()在语义上是一致的,但是在实际开发中,建议使用this.doGet()的方式来调用对象的方法。

3.实现HspCalServlet

public class HspCalServlet extends HspHttpServlet {
    @Override
    public void doGet(HspRequest request, HspResponse response) {
        //java基础的 OOP 的动态绑定机制..
        //写业务代码,完成计算任务
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

        int sum = num1 + num2;

        //返回计算结果给浏览器
        //outputStream 和 当前的socket关联
        OutputStream outputStream = response.getOutputStream();
        String respMes = HspResponse.respHeader
                + "<h1>" + num1 + " + " + num2 + " = " + sum + " HspTomcatV3 - 反射+xml创建</h1>";
        try {
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(HspRequest request, HspResponse response) {
        this.doGet(request, response);
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

WebUtils.java

public class WebUtils {

    //将字符串转成数字方法
    public static int parseInt(String strNum, int defaultVal) {
        try {
            return Integer.parseInt(strNum);
        } catch (NumberFormatException e) {
            System.out.println(strNum + " 不能转成数字");
        }
        return defaultVal;
    }
}

改进优化HspRequestHandler处理器

/**
 * 老师解读
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            //这里我们先死后活
            HspRequest hspRequest = new HspRequest(socket.getInputStream());

            //这里我们可以同HspResponse对象,返回数据给浏览器/客户端
            HspResponse hspResponse = new HspResponse(socket.getOutputStream());

            //创建HspCalServlet对象-> 一会我们再用反射来构建对象
            HspCalServlet hspCalServlet = new HspCalServlet();
            hspCalServlet.doGet(hspRequest, hspResponse);

            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

2.3.3.3 优化3阶段

1.构造容器
2.配置web.xml
3.创建容器(存放servlet和servlet-mapping)
4.利用dom4j技术获取xml文件,反射创建对象

思路分析
在这里插入图片描述
拷贝web.xml
在这里插入图片描述
引入dom4j的依赖

<!--导入dom4j-->
<dependency>
  <groupId>dom4j</groupId>
  <artifactId>dom4j</artifactId>
  <version>1.1</version>
</dependency>

web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <!--配置自己设计的Servlet
        提示: 因为这是我们自己的servlet , 所以不识别, 没有关系
        , 直接忽略爆红..
    -->
    <servlet>
        <servlet-name>HspCalServlet</servlet-name>
        <servlet-class>com.hspedu.tomcat.servlet.HspCalServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HspCalServlet</servlet-name>
        <url-pattern>/hspCalServlet</url-pattern>
    </servlet-mapping>
</web-app>

HspTomcatV3.java

/**
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class HspTomcatV3 {

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, HspHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //变强..
    public static void main(String[] args) {
        init();
    }

    //直接对两个容器进行初始化
    public static void init() {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = HspTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        //困难->真的掌握
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

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

        //验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

2.3.3.4 优化4阶段

1.完善自定义Tomcat
2.修改优化代码
3.完善测试web.xml

  • 改进HspTomcatV3
/**
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class HspTomcatV3 {

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, HspHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    
    //变强..
    public static void main(String[] args) {
        HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
        hspTomcatV3.init();
        //启动hsptomcat容器
        hspTomcatV3.run();
    }


    //启动HspTomcatV3容器
    public void run() {

        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====hsptomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                HspRequestHandler hspRequestHandler =
                        new HspRequestHandler(socket);
                new Thread(hspRequestHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    public void init() {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = HspTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        //困难->真的掌握
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

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

        //验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

  • 改进HspRequestHandler
/**
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {

            //这里我们先死后活
            HspRequest hspRequest = new HspRequest(socket.getInputStream());
            

            //这里我们可以同HspResponse对象,返回数据给浏览器/客户端
            HspResponse hspResponse = new HspResponse(socket.getOutputStream());

            //1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            String uri = hspRequest.getUri();

            String servletName = HspTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null) {
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
            HspHttpServlet hspHttpServlet =
                    HspTomcatV3.servletMapping.get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
            if (hspHttpServlet != null) {//得到
                hspHttpServlet.service(hspRequest, hspResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = HspResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = hspResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }


            
            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

  • 改进web.xml
<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <!--配置自己设计的Servlet
        提示: 因为这是我们自己的servlet , 所以不识别, 没有关系
        , 直接忽略爆红..
    -->
    <servlet>
        <servlet-name>HspCalServlet</servlet-name>
        <servlet-class>com.hspedu.tomcat.servlet.HspCalServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HspCalServlet</servlet-name>
        <url-pattern>/hspCalServlet</url-pattern>
    </servlet-mapping>
    <!--多配置一个servlet-->
    <servlet>
        <servlet-name>Hsp6CalServlet</servlet-name>
        <servlet-class>com.hspedu.tomcat.servlet.Hsp6CalServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Hsp6CalServlet</servlet-name>
        <url-pattern>/hsp6CalServlet</url-pattern>
    </servlet-mapping>
</web-app>

  • 增添Hsp6CalServlet
public class Hsp6CalServlet extends HspHttpServlet {
    @Override
    public void doGet(HspRequest request, HspResponse response) {
        //java基础的 OOP 的动态绑定机制..
        //写业务代码,完成计算任务
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

        int sum = num1 * num2;

        //返回计算结果给浏览器
        //outputStream 和 当前的socket关联
        OutputStream outputStream = response.getOutputStream();
        String respMes = HspResponse.respHeader
                + "<h1>" + num1 + " * " + num2 + " = " + sum + " HspTomcatV3 - 反射+xml创建</h1>";
        try {
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(HspRequest request, HspResponse response) {

    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

2.4 小结

回顾 Tomcat 工作架构图
在这里插入图片描述
BIO线程模型

BIO(Blocking I/O)即同步阻塞 I/O,是 Java 中最早使用的一种 I/O 模型。它的特点是使用传统的套接字(Socket)进行通信,当服务端接收到客户端请求后,为每个客户端连接创建一个新的线程进行处理,因此这种模型也被称为“一请求一应答”模式。

BIO 线程模型的流程如下:

  1. 服务端启动后,通过 ServerSocket 等待客户端连接请求。
  2. 客户端发起连接请求后,服务端接收到请求并创建一个新的线程(或使用线程池中的一个线程)处理客户端的请求。
  3. 服务端将接收到的数据存放到缓冲区中,并处理客户端请求。
  4. 服务端将处理结果返回给客户端,并关闭连接。

需要注意的是,BIO 模型中的线程数随着客户端连接数量的增加而增加,如果客户端连接数量过多,服务端可能会出现线程资源不足的情况。


NIO线程模型

NIO(Non-blocking I/O)即非阻塞 I/O,是 Java 中另一种 I/O 模型。相比于 BIO 模型,它使用单个线程或少量线程来处理多个客户端的请求,因此也被称为“多路复用”模式。

NIO 线程模型的流程如下:

  1. 服务端启动后,通过 ServerSocketChannel 等待客户端连接请求。
  2. 客户端发起连接请求后,服务端接收到请求,并将其注册到 Selector 中,由 Selector 监听客户端的事件。
  3. Selector 在监听到事件后,将其分发给对应的线程进行处理,例如读取客户端发送的数据。
  4. 完成对客户端请求的处理后,服务端将处理结果返回给客户端,并关闭连接。

需要注意的是,NIO 模型中使用单个线程或少量线程进行处理,因此可以避免频繁创建线程造成的开销,同时也能够提高系统的性能和吞吐量。但是,在处理客户端请求时需要使用较为复杂的事件轮询机制。

3. 课后作业

在这里插入图片描述
在这里插入图片描述
cal.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
<h1>计算器</h1>
<form action="/hspCalServlet" method="get">
    num1:<input type="text" name="num1"><br/>
    num2:<input type="text" name="num2"><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

web.xml

<servlet>
    <servlet-name>HspCalServlet</servlet-name>
    <servlet-class>com.hspedu.tomcat.servlet.HspCalServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>HspCalServlet</servlet-name>
    <url-pattern>/hspCalServlet</url-pattern>
</servlet-mapping>

WebUtils.java

public class WebUtils {

    //将字符串转成数字方法
    public static int parseInt(String strNum, int defaultVal) {
        try {
            return Integer.parseInt(strNum);
        } catch (NumberFormatException e) {
            System.out.println(strNum + " 不能转成数字");
        }
        return defaultVal;
    }

    //判断uri是不是html文件
    public static boolean isHtml(String uri) {

        return uri.endsWith(".html");
    }

    //根据文件名来读取该文件->String
    public static String readHtml(String filename) {
        String path = com.hspedu.utils.WebUtils.class.getResource("/").getPath();
        StringBuilder stringBuilder = new StringBuilder();

        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path + filename));
            String buf = "";
            while ((buf = bufferedReader.readLine()) != null) {
                stringBuilder.append(buf);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stringBuilder.toString();
    }
}

HspRequestHandler.java

/**
 * 老师解读
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
public class HspRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

    public HspRequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {

            //这里我们先死后活
            HspRequest hspRequest = new HspRequest(socket.getInputStream());
  
            //这里我们可以同HspResponse对象,返回数据给浏览器/客户端
            HspResponse hspResponse = new HspResponse(socket.getOutputStream());

            //1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            String uri = hspRequest.getUri();



            //=====================新增业务逻辑==========
            //(1) 判断uri是什么资源 => 工具方法
            //(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
            //(3) 因为目前老师并没有起到tomcat, 不是一个标准的web项目
            //(4) 把读取的静态资源放到 target/classes/cal.html
            //拓展:过滤,拦截 , 权限等待 => Handler.... => 分发
            if(WebUtils.isHtml(uri)) {//就是静态页面
                String content = WebUtils.readHtml(uri.substring(1));
                content = HspResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = hspResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }



            String servletName = HspTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null) {
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
            HspHttpServlet hspHttpServlet =
                    HspTomcatV3.servletMapping.get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
            if (hspHttpServlet != null) {//得到
                hspHttpServlet.service(hspRequest, hspResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = HspResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = hspResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
/**
 * @author 韩顺平
 * @version 1.0
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class HspTomcatV3 {

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, HspHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //你可以这里理解session, tomcat还维护一个容器
    public static final ConcurrentHashMap<String, HttpSession>
            sessionMapping = new ConcurrentHashMap<>();
    

    //你可以这里理解filter, tomcat还维护了filter的容器
    public static final ConcurrentHashMap<String, String>
            filterUrlMapping = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, Filter>
            filterMapping = new ConcurrentHashMap<>();
    //变强..
    public static void main(String[] args) {
        HspTomcatV3 hspTomcatV3 = new HspTomcatV3();
        hspTomcatV3.init();
        //启动hsptomcat容器
        hspTomcatV3.run();
    }


    //启动HspTomcatV3容器
    public void run() {

        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====hsptomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                HspRequestHandler hspRequestHandler =
                        new HspRequestHandler(socket);
                new Thread(hspRequestHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    public void init() {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = HspTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        //困难->真的掌握
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            Element rootElement = document.getRootElement();
            //得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            //遍历并过滤到 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet配置
                    //System.out.println("发现 servlet");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (HspHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

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

        //验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

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

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

相关文章

【MySQL】带你了解MySQL 如何学习MySQL以及MySQL的用途以及意义

目录 1 MySQL的起源和发展 1.0.1 数据库管理系统 1.1 MySQL的起源 命名由来&#xff1a; 1.2 MySQL的发展历程 2 什么是MySQL&#xff1f; 2.1 数据库 2.1.1 我们之前存储数据的格式&#xff1a; 2.1.2 使用数据库的目的&#xff1a; 2.1.3 数据库分类 2.2 SQL语句 2…

STM32-HAL-串口的printf重定向

一、C语言的格式化输出 C语言的printf是一个标准库函数&#xff0c;用于将格式化的数据输出到标准的输出设备&#xff08;通常是终端&#xff09; 基本语法&#xff1a; int printf(const char *format, ...);其中的第一个参数const char *format表示输出格式&#xff0c;后面…

Kubernetes核心组件及资源介绍

文章目录 一、Kubernetes架构二、Kubernetes核心组件三、Kubernetes核心资源四、拓展1、Service和Ingress的区别是什么&#xff1f;2、Replicaset和Deployment的区别是什么&#xff1f;3、Deployment和Statefulset的区别是什么&#xff1f;4、Job和Cronjob的区别是什么&#xf…

【FAQ】统一扫码服务常见问题及解答

1.隐私政策是怎么样的&#xff1f;收集哪些信息&#xff1f; 关于Scan Kit的隐私政策及收集的信息&#xff0c;请查看SDK隐私安全说明。 Android&#xff1a;SDK隐私安全说明 iOS&#xff1a;SDK隐私安全说明 2.如何使用多码识别&#xff1f;多码模式下如何实现指定条码&am…

vue生命周期的理解?

目录标题 一、什么是生命周期&#xff1f;二、生命周期的流程图&#xff1a; 一、什么是生命周期&#xff1f; Vue 实例有一个完整的生命周期&#xff0c;也就是从开始创建、初始化数据、编译版、挂载Dom ->染、更新 ->渲染、卸载 等一系列过程&#xff0c;称这是Vue的生…

简单清晰了解B树和B+树

一.学习背景 在MySQL的学习中,我们了解到了索引的知识,而关于MySQL索引背后的数据结构,我们在这里进行学习. 首先,我们要了解到的是,MySQL的索引用到的数据结构为B树. 使用B树是因为,在数据量大的时候,内存不够用&#xff0c;大部分数据只能存放在磁盘上&#xff0c;只有需要…

【Linux命令行与Shell脚本编程】第七章 Linux文件权限

Linux命令行与Shell脚本编程 第七章 Linux文件权限 文章目录 Linux命令行与Shell脚本编程七,Linux文件权限7.1,Linux的安全性7.1.1,用户信息 /etc/passwd文件7.1.2,用户密码 /etc/shadow文件7.1.3,useradd 添加新用户7.1.4,userdel 删除用户7.1.5, 修改用户1,usermod2,passwd…

Hystrix Dashboard图形化监控

&#x1f449;&#x1f3fb; SpringCloud 入门实战系列不迷路 &#x1f448;&#x1f3fb;&#xff1a; SpringCloud 入门实战&#xff08;一&#xff09;什么是SpringCloud&#xff1f;SpringCloud 入门实战&#xff08;二&#xff09;-SpringCloud项目搭建SpringCloud 入门实…

【云计算•云原生】1.什么是云计算?它为什么这么火?

文章目录 1.云计算基础什么是云计算云计算的分类开源软件、自由软件、免费软件 2.云计算机制云基础设施机制云管理机制云安全机制基本云架构 3.虚拟化技术服务器虚拟化服务器虚拟化技术CPU虚拟化内存虚拟化设备和I/O虚拟化存储虚拟化网络虚拟化桌面虚拟化 KVM 4.网络与存储基础…

90后,第一批接棒白酒的“年轻人”

【潮汐商业评论/原创】 又到了每月的公司团建&#xff0c;“团建必喝酒”似乎已经成为了大家的共识。 但在酒水的选择上&#xff0c;有红的、啤的、洋的&#xff0c;还有低度果酒&#xff0c;唯独不见听谁说聚会喝白的。当Jason询问为什么没有白酒时&#xff0c;得到了几乎一…

C语言函数大全-- o 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- o 开头的函数 1. obstack_init&#xff0c;obstack_free&#xff0c;obstack_alloc&#xff0c;obstack_blank&#xff0c;obstack_grow 1.1 函数说明 函数声明函数功能void obstack_init(struct obstack *obstack_ptr);它是 POSIX …

Python采集某网站小sp内容, m3u8内容下载

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 环境使用: Python 3.8 Pycharm 模块使用: import requests >>> pip install requests import re 正则表达式 解析数据 import json 本次案例所用知识点: python基础语法: 字典创建/取值 print输出函数使用…

【人工智能概论】 RNN、LSTM、GRU简单入门与应用举例、代码耗时计算

【人工智能概论】 RNN、LSTM、GRU简单入门与应用举例、代码耗时计算 文章目录 【人工智能概论】 RNN、LSTM、GRU简单入门与应用举例、代码耗时计算一. RNN简介1.1 概念简介1.2 方法使用简介 二. 编码层embedding2.1 embedding的参数2.2 embedding的理解 三. Linear层与CrossEnt…

全景图像算法简述

全景图像拼接是一种将多个图像合成为一个大型全景图像的技术。这种技术广泛应用于虚拟现实、游戏开发、文化遗产保护等领域。本文将介绍三种主流的全景图像拼接算法&#xff0c;并对它们进行综合比较。 一、基于特征点匹配的拼接算法 特征点匹配是全景图像拼接中最常用的一种算…

深入讲解ARMv8 异常处理简介

内核稳定性问题复杂多样&#xff0c;最常见的莫过于“kernel panic”&#xff0c;意为“内核恐慌&#xff0c;不知所措”。这种情况下系统自然无法正常运转&#xff0c;只能自我结束生命&#xff0c;留下死亡信息。诸如&#xff1a; “Unable to handle kernel XXX at virtual …

直播系统开发中哪些技术架构是必不可少的

在当今数字化时代&#xff0c;直播已成为一种极其受欢迎的娱乐和营销方式。由于直播应用的复杂性&#xff0c;架构师在直播系统的开发和设计中扮演着至关重要的角色。本文将介绍直播系统开发中技术架构师所必不可少的技术和工具。 什么是直播系统&#xff1f; 直播系统是一种通…

uni-app入门到实战

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f35f;欢迎来到前端初见的博文&#xff0c;本文主要讲解uni-app入门到实战&#x1f35f; &#x1f468;‍&#x1f527; 个人主页 : 前端初见 &#x1f95e;喜欢的朋友可以关注一下&#xff…

【Linux】浏览器写代码!部署code-server远程vscode网页

部署code-server远程vscode网页&#xff0c;在浏览器上写代码&#xff01; 参考文档 https://developer.aliyun.com/article/876967#slide-7 本文首发于 慕雪的寒舍 1.什么是code-server&#xff1f; 注意&#xff0c;这不是在linux系统上安装vscode软件&#xff08;和windo…

虹科分享 | 如何主动保护个人信息 | 网络安全评级

网上报税和支付越来越流行&#xff0c;针对毫无防备的纳税人的税务欺诈也越来越频繁。以下是一些需要避免的常见网上税务骗局&#xff1a; 网络钓鱼诈骗 骗子利用电子邮件、短信或电话伪装成相关机构或报税软件提供商&#xff0c;诱骗人们提供他们的个人信息&#xff0c;或点击…

走进梦龙冰淇淋的生产线 揭晓“灯塔工厂”背后的秘密

作为一家积极履行社会责任的公司&#xff0c;联合利华和路雪在今年3月携手京东“青绿计划”&#xff0c;推出了包含梦龙、可爱多、千层雪、和路雪等多款可持续组合装产品&#xff0c;这一助力实现双碳目标的举措也引发了消费者的关注。其实&#xff0c;自联合利华太仓食品生产基…