java手写简易Tomcat服务器+Servlet(思路流程图+超详细注释)一步步实现,简单易懂最后有源码和视频讲解


前言

手动实现 Tomcat 底层机制+ 自己设计 Servlet。模拟Tomcat底层实现并能调用我们自己设计的Servlet实现一个简单的小案例。
一步步循序渐进,图解+分析+代码分三个阶段(三个版本 循序渐进)来实现.
所运用到的知识:


先看看最终版效果图
输入有效的地址访问正确
在这里插入图片描述
输入无效的地址返回404 not Found
在这里插入图片描述

文件结构
在这里插入图片描述


一、Tomcat整体架构分析

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

总体思路流程图:

流程图

二、实现Tomcat底层机制+设计Servlet

1、阶段1——拿到客户端/浏览器发送的请求,回送"Hello World!"

1.1 基于socket开发服务端-流程

在这里插入图片描述

1.2 需求分析/图解

需求如图:在这里插入图片描述

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

1.3 分析+代码实现

项目准备:新建一个web项目,建一个cat.html和LhTomcat.java

项目结构(LhTomcatV1.java)
在这里插入图片描述

cat.html

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

LhtomcatV1.java

package xyz.lhweb.lhTomcat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * lh tomcat
 *第一个版本的tomcat 可以完成接受浏览器发送的请求,并返回相关信息
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/01
 */
public class LhTomcatV1 {
    public static void main(String[] args) throws IOException {
        //1 创建ServletSocket 在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("==========================lhTomcat在8080端口监听======================");
        // 如果没有关闭就一直在等待监听
        while (!serverSocket.isClosed()){
            /**
             * 等待浏览器/客户端的连接
             * 如果有连接来,就创建一个socket
             * 这个socket就是服务端和浏览器/客户端之间的通道
             */
            Socket socket = serverSocket.accept();
            /**
             * 先接收浏览器发送的信息
             * inputStream是字节流 为了读取方便转成一个BuffreadReader字符流。可以整行整行的读取
             */
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            String msg=null;
            System.out.println("==================接收到浏览器发送的数据=================");
            //循环的读取
            while ((msg=bufferedReader.readLine())!=null){
                //判断mes的长度是否为0
                if(msg.length()==0){
                    break;
                }
                System.out.println(msg);
            }
            //我们的tomcat回送http响应方式
            OutputStream outputStream = socket.getOutputStream();
            /**
             * 因为要按照http响应的方式,所以要构建一个http响应的消息头
             * \r\n代表换行
             * 响应体和响应行之间有一个空行 所以是\r\n\r\n 相当于换行后空一行
             */
            String respHeader="HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp=respHeader+"Hello World!";
            //将resp字符串以byte[]方式返回
            outputStream.write(resp.getBytes());
            //关闭流
            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();
        }
    }
}

效果展示
在这里插入图片描述

在这里插入图片描述

1.4 测试 浏览器:http://localhost:8080/

1.5 问题分析

没有使用BIO线程模型,没有实现多线程,性能差
目标:拿到客户端/浏览器发送的请求,回送"Hello World!"

2、阶段2——使用BIO线程模型实现多线程

2.1 BIO线程模型介绍

在这里插入图片描述

2.2 需求分析/图解

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

2.3 分析+代码实现

分析:原来是在lhtomcat对数据的返回和处理,现在改到在线程中对数据的返回和处理。现在的lhtomcat相当于一个前台的身份,不具体的负责业务处理。只负责把Socket通道传给线程。
项目结构
在这里插入图片描述
LhRequestHandler.java

package xyz.lhweb.lhTomcat.handler;

import java.io.*;
import java.net.Socket;


/**
 * lh请求处理程序
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandler implements Runnable{
    //定义socket
    private Socket socket=null;

    public LhRequestHandler(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("==================lhtomcat2 接收到的数据如下====================");
            String msg=null;
            //如果我读取到的信息不为空,说明可以继续读取
            while ((msg=bufferedReader.readLine())!=null){
                    //如果长度为0 "" 退出
                if(msg.length()==0){
                    break;
                }
                System.out.println(msg);
            }
            //构建一个响应头
            //返回的http的响应体和响应头之间有2个换行\r\n\r\n
            String respHeader="HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp=respHeader+"<h1>hi lhTomcatV2</h1>";
            System.out.println("==================lhtomcat2 返回的数据如下====================");
            System.out.println(resp);
            //从socket返回数据给我们的浏览器/客户端 封装成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) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

LhTomcatV2.java

package xyz.lhweb.lhTomcat;

import xyz.lhweb.lhTomcat.handler.LhRequestHandler;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * lh tomcat
 *第一个版本的tomcat 可以完成接受浏览器发送的请求,并返回相关信息
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhTomcatV2 {
    public static void main(String[] args) throws IOException {
        //1 创建ServletSocket 在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("==========================lhTomcatV2在8080端口监听======================");
        // 如果没有关闭就一直在等待监听
        while (!serverSocket.isClosed()){
            /**
             * 1 等待浏览器/客户端的连接如果有连接来,就创建一个socket
             * 2 这个socket就是服务端和浏览器/客户端之间的通道
             */
            Socket socket = serverSocket.accept();
            //3 创建一个线程对象,并且吧socket传给线程
            new Thread(new LhRequestHandler(socket)).start();
        }
    }
}

效果图
在这里插入图片描述
因为接收到了小图标的请求 所以会返回2次

2.4 测试 浏览器: http://localhost:8080/

在这里插入图片描述

2.5 问题分析:

lhTomcatV2只是简单返回结果,没有和Servlet、web.xml关联。每次请求都会开启一个线程,不是多路复用的。BIO是有一点问题的,没有NIO(多路复用)好用。将在第三阶段引入servlet

3、阶段3——处理Servlet及使用容器(特别重要)

3.1 Servlet生命周期-回顾

在这里插入图片描述

3.2 需求分析/图解

图解:
在这里插入图片描述
先搭建结构,再编写内容
定义接口,抽象类,实现类(注:实现类为LhServletV3。不能和接口同名)
![在这里插入图片描述](https://img-blog.csdnimg.cn/22560517f83d41428aea9c91b81134ff.png
类图如下
在这里插入图片描述

3.3 分析+代码实现

1 先解决request和response

浏览器给tomcat发起请求后,tomcat会把请求封装成HttpServletRequest对象,返回数据是HttpServletResponse对象

原生的sevlet里有这2个接口,我们需要自己定义这2个对象来作代替
在这里插入图片描述

3.3.1封装Request

LhRequest.java编写思路
首先在构建LhRequest对象的时候 构造器 =>对http请求进行封装
获取到请求的第一行然后分割提取
请求方式method
地址url
参数列表parametersMap

代码展示

LhRequest.java

package xyz.lhweb.lhTomcat.http;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

/**
 * lh请求
 * 1 LhRequest作用是封装http请求的数据
 * 2 比如 method get/post uri,参数列表
 * 例如:http://localhost:8080/lhServletV3?name=1&password=2 封装的就是 /lhServletV3?name=1&password=2
 * 3 LhRequest等价原生servlet 中的HttpServletRequest
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 * 4 现在只考虑get请求
 */
public class LhRequest {
    private String method;
    private String uri;
    private InputStream inputStream=null;
    //参数列表 key value 所以用hashMap
    private final HashMap<String,String>parametersMap=
            new HashMap<>();
    //构造器 =>对http请求进行封装
    //inputStream是和对应的 http请求的socket关联
    public LhRequest(InputStream inputStream){
        this.inputStream=inputStream;
        //完成对http请求数据的封装
        init();
    }
    private void init(){

        try {
            //为了读取方便 inputStream-> BufferedReader  InputStreamReader: 转换流
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
            //读取第一行
            /*原始请求 http://localhost:8080/lhServletV3?name=1&password=2
            * GET /lhServletV3?name=1&password=2 HTTP/1.1
                Host: localhost:8080
            * */
            String requestLine = bufferedReader.readLine();//GET /lhServletV3?name=1&password=2 HTTP/1.1
            //按照空格分成一个数组
            String[] requestLineArr=null;
            if(requestLine!=null){
                requestLineArr = requestLine.split(" ");
            }
            //得到method
            if(requestLineArr!=null){
                method=requestLineArr[0];
            }
            //得到uri:/lhServletV3 可以用正则表达式
            //看看是否有参数列表
            assert requestLineArr != null;
            int index=requestLineArr[1].indexOf("?");
            if (index==-1){//说明没有参数列表
                uri=requestLineArr[1];
            }else {//有参数列表
                //[0,index)
                uri=requestLineArr[1].substring(0,index);//取了/lhServletV3
                //获取参数列表 放到parametersMap中去
                //parameters 相当于 name=1&password=2
                String parameters = requestLineArr[1].substring( index + 1);
                String[] parametersPair = parameters.split("&");
                //防止用户提交时http://localhost:8080/lhServletV3?   后面只给?不给参数
                // if(null!=parametersPair&& "".equals(parametersPair)){
                    //再次分割 parameterPair=    name=1
                    for (String parameterPair : parametersPair) {
                        //parameterVal["name","1"]
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length==2){//说明的的确确有参数值
                            //放入到parametersMap里去
                            parametersMap.put(parameterVal[0],parameterVal[1]);
                        }
                    // }
                }
            }

        }  catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    // request对象有一个特别重要的方法
    public String getParameter(String name){
        if(parametersMap.containsKey(name)){
            return parametersMap.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 "LhRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMap=" + parametersMap +
                '}';
    }
}

LhRequestHandlerV3_TestLhRequest.java

package xyz.lhweb.lhTomcat.handler;

import xyz.lhweb.lhTomcat.http.LhRequest;

import java.io.*;
import java.net.Socket;


/**
 * 对LhRequest单独进行测试
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandlerV3_TestLhRequest implements Runnable{
    //定义socket
    private Socket socket=null;
    public LhRequestHandlerV3_TestLhRequest(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run(

    ) {
        // 这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            InputStream inputStream = socket.getInputStream();
            //对LhRequest进行测试
            LhRequest lhRequest = new LhRequest(inputStream);
            String name = lhRequest.getParameter("name");
            String password = lhRequest.getParameter("password");
            System.out.println("LhRequest测试:--------------------------name="+name+"---------------------------");
            System.out.println("LhRequest测试:----------------------password="+password+"---------------------------");
            //构建一个响应头
            //返回的http的响应体和响应头之间有2个换行\r\n\r\n
            String respHeader="HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String resp=respHeader+"<h1>hi lhTomcatV2</h1>";
            System.out.println("==================lhtomcat2 返回的数据如下====================");
            System.out.println("LhRequest测试:----------------------LhRequest="+lhRequest+"---------------------------");
            System.out.println(resp);
            //从socket返回数据给我们的浏览器/客户端 封装成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) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

LhTomcatV3_TestLhRequest.java

package xyz.lhweb.lhTomcat.handler;

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

/**
 * lh tomcat v3测试lh请求
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhTomcatV3_TestLhRequest {
    public static void main(String[] args) throws IOException {
        //1 创建ServletSocket 在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("==========================LhTomcatV3_TestLhRequest在8080端口监听======================");
        // 如果没有关闭就一直在等待监听
        while (!serverSocket.isClosed()){
            /**
             * 1 等待浏览器/客户端的连接如果有连接来,就创建一个socket
             * 2 这个socket就是服务端和浏览器/客户端之间的通道
             */
            Socket socket = serverSocket.accept();
            //3 创建一个线程对象,并且吧socket传给线程
            new Thread(new LhRequestHandlerV3_TestLhRequest(socket)).start();
        }
    }
}

效果测试

LhTomcatV3_TestLhRequest里面测试
在这里插入图片描述
浏览器输入:http://localhost:8080/LhTomcatV3_TestLhRequest?name=1&password=2
在这里插入图片描述

3.3.2 封装Response
代码展示

LhResponse.java

package xyz.lhweb.lhTomcat.http;

import java.io.OutputStream;

/**
 * lh响应
 * <p>
 * 1 LhResponse对象可以封装OutputStream(是socket关联),
 * 2 即可以通过 HspResponse对象 返回Http响应浏览器/客户端
 * 3 LhResponse对象的作用等价于原生的servlet的 HttpServletResponse
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhResponse {
    private OutputStream outputStream = null;

    //写一个http的响应头 =>
    //如果有兴趣,在编写更多的方法 比如setContentTupe
    public static final String respHeader="HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    // 在创建LhResponse时 传入的outputStream是和Socket关联的
    public LhResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    // 当我们需要给浏览器返回数据时 可以通过LhResponse对象 的输出流完成
    public OutputStream getOutputStream() {
        return outputStream;
    }
}

LhRequestHandlerV3_TestLhResponse.java

package xyz.lhweb.lhTomcat.handler;

import xyz.lhweb.lhTomcat.http.LhResponse;

import java.io.*;
import java.net.Socket;


/**
 * lh请求处理程序
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandlerV3_TestLhResponse implements Runnable{
    //定义socket
    private Socket socket=null;
    public LhRequestHandlerV3_TestLhResponse(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("==================lhtomcatV3 接收到的数据如下====================");
            String msg=null;
            //如果我读取到的信息不为空,说明可以继续读取
            while ((msg=bufferedReader.readLine())!=null){
                    //如果长度为0 "" 退出
                if(msg.length()==0){
                    break;
                }
                System.out.println(msg);
            }

            //这里我们可以通过LhResponse对象,返回数据给浏览器/客户端
            LhResponse lhResponse = new LhResponse(socket.getOutputStream());
            String resp = LhResponse.respHeader + "<h1>lhResponse 返回的信息 hi LhRequestHandlerV3_TestLhResponse<h1>";
            OutputStream outputStream = lhResponse.getOutputStream();
            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) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

效果测试

浏览器输入 http://localhost:8080/
在这里插入图片描述

3.3.2 封装servlet
代码展示

LhServletV3.java

package xyz.lhweb.lhTomcat.servlet;

import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;

import java.io.IOException;
import java.io.OutputStream;

/**
 * lh servlet v3
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhServletV3 extends LhHttpServlet{
    public LhServletV3() throws Exception {
        init();
    }

    @Override
    public void doGet(LhRequest req, LhResponse res) {
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        //outputStream和socket关联
        OutputStream outputStream = res.getOutputStream();
        String respHeader = LhResponse.respHeader+"<h1>姓名:"+name+"密码:"+password+"</h1>";
        try {
            outputStream.write(respHeader.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doPost(LhRequest req, LhResponse res) {
        this.doGet(req, res);
    }

    @Override
    public void init() throws Exception {
        System.out.println("init方法被调用");
    }

    @Override
    public void destroy() {

    }
}

LhRequestHandlerV3_TestLhServlet.java

package xyz.lhweb.lhTomcat.handler;

import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;
import xyz.lhweb.lhTomcat.servlet.LhServletV3;

import java.io.*;
import java.net.Socket;


/**
 * lh请求处理程序
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandlerV3_TestLhServlet implements Runnable {
    // 定义socket
    private Socket socket = null;

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

    @Override
    public void run(

    ) {
        // 这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            InputStream inputStream = socket.getInputStream();
            LhRequest lhRequest = new LhRequest(inputStream);
            // 这里我们可以通过LhResponse对象,返回数据给浏览器/客户端
            LhResponse lhResponse = new LhResponse(socket.getOutputStream());
            // 创建LhServletV3对象 之后要用反射实现
            LhServletV3 lhServletV3 = new LhServletV3();
            lhServletV3.doGet(lhRequest, lhResponse);

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

测试代码
LhTomcatV3_TestLhServletV3.java

package xyz.lhweb.lhTomcat.handler;

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

/**
 * lh tomcat v3测试lh请求
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhTomcatV3_TestLhServletV3 {
    public static void main(String[] args) throws IOException {
        //1 创建ServletSocket 在8080端口监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("==========================LhRequestHandlerV3_TestLhServlet在8080端口监听======================");
        // 如果没有关闭就一直在等待监听
        while (!serverSocket.isClosed()){
            /**
             * 1 等待浏览器/客户端的连接如果有连接来,就创建一个socket
             * 2 这个socket就是服务端和浏览器/客户端之间的通道
             */
            Socket socket = serverSocket.accept();
            //3 创建一个线程对象,并且吧socket传给线程
            new Thread(new LhRequestHandlerV3_TestLhServlet(socket)).start();
        }
    }
}

效果测试

浏览器输入:

3.3.3 容器设计

在这里插入图片描述
一定要记得将web,xml复制到target/classes下
在这里插入图片描述

代码展示

LhTomcatV3.java

package xyz.lhweb.lhTomcat;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import xyz.lhweb.lhTomcat.handler.LhRequestHandlerV3_Test;
import xyz.lhweb.lhTomcat.servlet.LhServletV3;

import java.io.File;
import java.io.IOException;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;


/**
 * lh tomcat v3
 * 第三版,实现通过xml+反射来初始化容器
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/16
 */
public class LhTomcatV3 {
    public static void main(String[] args) {
        LhTomcatV3 lhTomcatV3 = new LhTomcatV3();
        lhTomcatV3.init();
        // 启动LhTomcatV3容器
        lhTomcatV3.run();
    }

    // 启动LhTomcatV3容器
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("======================LhTomcatV3在8080监听==========================");
            // 没有关闭掉就循环监听
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                LhRequestHandlerV3_Test lhRequestHandlerV3_test = new LhRequestHandlerV3_Test(socket);
                new Thread(lhRequestHandlerV3_test).start();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 第一个容器
     * 容器 servletMapping
     * -ConcurrentHashMap
     * -HashMap
     * key               -valueServletName 对应的实例
     */
    public static final HashMap<String, LhServletV3> servletMapping = new HashMap<>();
    /**
     * 第一个容器
     * 容器 servletUrlMapping
     * -ConcurrentHashMap
     * -HashMap
     * key -valueurl    -pattern ServletName
     */
    public static final HashMap<String, String> servletUrlMapping = new HashMap<>();

    // 初始化2个容器
    public void init() {
        // 用dom4j解析xml
        String path = LhTomcatV3.class.getResource("/").getPath();
        // System.out.println(path);
        SAXReader saxReader = new SAXReader();
        // web.xml 在target/classes 下
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            // System.out.println(document);// 查看是否能找到web.xml
            Element rootElement = document.getRootElement();// 拿到根元素 web-app
            // 得到根元素下面的所有元素
            List<Element> elements = rootElement.elements();
            // 遍历并过滤
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    // 这是一个servlet配置
                    // System.out.println("发现了servlet");
                    // 使用反射将serlvet实例放入到 servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(), (LhServletV3) 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 urlPattern = element.element("url-pattern");
                    // 注意 url作为key name作为值
                    servletUrlMapping.put(urlPattern.getText().trim(), servletName.getText());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 验证 2个容器是否初始化成功
        System.out.println("servletMapping:   " + servletMapping);
        System.out.println("servletUrlMapping    " + servletUrlMapping);
    }
}

LhServletV3.java

package xyz.lhweb.lhTomcat.servlet;

import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;

import java.io.IOException;
import java.io.OutputStream;

/**
 * lh servlet v3
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhServletV3 extends LhHttpServlet{
    public LhServletV3() throws Exception {
        init();
    }

    @Override
    public void doGet(LhRequest req, LhResponse res) {
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        //outputStream和socket关联
        OutputStream outputStream = res.getOutputStream();
        String respHeader = LhResponse.respHeader+"<h1>姓名:"+name+"密码:"+password+"-----------------lhServletV3-反射+xml创建"+"</h1>";
        try {
            outputStream.write(respHeader.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doPost(LhRequest req, LhResponse res) {
        this.doGet(req, res);
    }

    @Override
    public void init() throws Exception {
        // System.out.println("init方法被调用");
    }

    @Override
    public void destroy() {

    }
}

LhRequestHandlerV3_Test

package xyz.lhweb.lhTomcat.handler;

import xyz.lhweb.lhTomcat.LhTomcatV3;
import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;
import xyz.lhweb.lhTomcat.servlet.LhHttpServlet;
import xyz.lhweb.lhTomcat.servlet.LhServletV3;

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


/**
 * lh请求处理程序
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandlerV3_Test implements Runnable {
    // 定义socket
    private Socket socket = null;

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

    @Override
    public void run(

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

            LhRequest lhRequest = new LhRequest(socket.getInputStream());
            // 这里我们可以通过LhResponse对象,返回数据给浏览器/客户端
            LhResponse lhResponse = new LhResponse(socket.getOutputStream());
            // 反射实现
            // 1 得到uri=> servletUrlMapping 的url-pattern
            String uri = lhRequest.getUri();
            // key-uri    value-servletName
            String servletName = LhTomcatV3.servletUrlMapping.get(uri);
            System.out.println("servletName:"+servletName);
            // 2 通过uri->servletName->servlet实例
            // 编译类型是LhHttpServlet ,!!!!!!!!!!!!!真正的运行类型是其子类
            LhHttpServlet lhHttpServlet =
                    LhTomcatV3.servletMapping.get(servletName);
            // 3 调用service方法 通过oop的动态绑定 调用运行类型的doGet(),doPost()
            if(lhHttpServlet!=null){
                lhHttpServlet.service(lhRequest, lhResponse);
            }else {
                //uri是瞎写的 不存在 没有这个servlet 返回404信息
                String resp=LhResponse.respHeader+"<h1>404 not Foud</h1>";
                OutputStream outputStream = lhResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

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

效果展示

运行LhTomcatV3
浏览器输入:
http://localhost:8080/lhServletV3?name=luohan&password=123
在这里插入图片描述
http://localhost:8080/lhServletV99
在这里插入图片描述
在这里插入图片描述
第2个和第4个是请求icon的Null

3.4 v3版本完成进行测试

新建一个servlet继承V3版本
进行xml配置

代码展示

LhServletV3Demo01.java

package xyz.lhweb.lhTomcat.servlet;

import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;

import java.io.IOException;
import java.io.OutputStream;

public class LhServletV3Demo01 extends LhServletV3{

    public LhServletV3Demo01() throws Exception {
    }

    @Override
    public void doGet(LhRequest req, LhResponse res) {
        String a = req.getParameter("a");
        String b = req.getParameter("b");
        //outputStream和socket关联
        OutputStream outputStream = res.getOutputStream();
        String respHeader = LhResponse.respHeader+"<h1>a:"+a+"+"+b+"="+(Integer.parseInt(a)+Integer.parseInt(b))+"-----------------lhServletV3_反射+xml创建"+"</h1>";
        try {
            outputStream.write(respHeader.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doPost(LhRequest req, LhResponse res) {
        this.doGet(req, res);
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--    配置自己设计的servlet
    爆红不管他 自己设置的不识别
    直接忽略
    -->
    <servlet>
        <servlet-name>LhServletV3</servlet-name>
        <servlet-class>xyz.lhweb.lhTomcat.servlet.LhServletV3</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LhServletV3</servlet-name>
        <url-pattern>/lhServletV3</url-pattern>
    </servlet-mapping>
    <!--测试LhServletV3Demo01-->
    <servlet>
        <servlet-name>LhServletV3Demo01</servlet-name>
        <servlet-class>xyz.lhweb.lhTomcat.servlet.LhServletV3Demo01</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LhServletV3Demo01</servlet-name>
        <url-pattern>/lhServletV3Demo01</url-pattern>
    </servlet-mapping>
</web-app>

LhTomcatV3.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package xyz.lhweb.lhTomcat;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import xyz.lhweb.lhTomcat.handler.LhRequestHandlerV3_Test;
import xyz.lhweb.lhTomcat.servlet.LhServletV3;

public class LhTomcatV3 {
    public static final HashMap<String, LhServletV3> servletMapping = new HashMap();
    public static final HashMap<String, String> servletUrlMapping = new HashMap();

    public LhTomcatV3() {
    }

    public static void main(String[] args) {
        LhTomcatV3 lhTomcatV3 = new LhTomcatV3();
        lhTomcatV3.init();
        lhTomcatV3.run();
    }

    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("======================LhTomcatV3在8080监听==========================");

            while(!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                LhRequestHandlerV3_Test lhRequestHandlerV3_test = new LhRequestHandlerV3_Test(socket);
                (new Thread(lhRequestHandlerV3_test)).start();
            }

        } catch (IOException var4) {
            throw new RuntimeException(var4);
        }
    }

    public void init() {
        String path = LhTomcatV3.class.getResource("/").getPath();
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            Element rootElement = document.getRootElement();
            List<Element> elements = rootElement.elements();
            Iterator var6 = elements.iterator();

            while(var6.hasNext()) {
                Element element = (Element)var6.next();
                Element servletName;
                Element urlPattern;
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    servletName = element.element("servlet-name");
                    urlPattern = element.element("servlet-class");
                    servletMapping.put(servletName.getText(), (LhServletV3)Class.forName(urlPattern.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    servletName = element.element("servlet-name");
                    urlPattern = element.element("url-pattern");
                    servletUrlMapping.put(urlPattern.getText().trim(), servletName.getText());
                }
            }
        } catch (Exception var10) {
            throw new RuntimeException(var10);
        }

        System.out.println("servletMapping:   " + servletMapping);
        System.out.println("servletUrlMapping    " + servletUrlMapping);
    }
}

LhServletV3.java

package xyz.lhweb.lhTomcat.servlet;

import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;

import java.io.IOException;
import java.io.OutputStream;

/**
 * lh servlet v3
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhServletV3 extends LhHttpServlet{
    public LhServletV3() throws Exception {
        init();
    }

    @Override
    public void doGet(LhRequest req, LhResponse res) {
        String name = req.getParameter("name");
        String password = req.getParameter("password");
        //outputStream和socket关联
        OutputStream outputStream = res.getOutputStream();
        String respHeader = LhResponse.respHeader+"<h1>姓名:"+name+"密码:"+password+"-----------------lhServletV3-反射+xml创建"+"</h1>";
        try {
            outputStream.write(respHeader.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void doPost(LhRequest req, LhResponse res) {
        this.doGet(req, res);
    }

    @Override
    public void init() throws Exception {
        // System.out.println("init方法被调用");
    }

    @Override
    public void destroy() {

    }
}

LhRequestHandlerV3.java

package xyz.lhweb.lhTomcat.handler;

import xyz.lhweb.lhTomcat.LhTomcatV3;
import xyz.lhweb.lhTomcat.http.LhRequest;
import xyz.lhweb.lhTomcat.http.LhResponse;
import xyz.lhweb.lhTomcat.servlet.LhHttpServlet;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;


/**
 * lh请求处理程序
 *
 * @author 罗汉 QQ;1072344372
 * @date 2023/03/09
 */
public class LhRequestHandlerV3 implements Runnable {
    // 定义socket
    private Socket socket = null;

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

    @Override
    public void run(

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

            LhRequest lhRequest = new LhRequest(socket.getInputStream());
            // 这里我们可以通过LhResponse对象,返回数据给浏览器/客户端
            LhResponse lhResponse = new LhResponse(socket.getOutputStream());
            // 反射实现
            // 1 得到uri=> servletUrlMapping 的url-pattern
            String uri = lhRequest.getUri();
            // key-uri    value-servletName
            String servletName = LhTomcatV3.servletUrlMapping.get(uri);
            if (servletName==null){
                servletName="null";
            }
            System.out.println("servletName:"+servletName);
            // 2 通过uri->servletName->servlet实例
            // 编译类型是LhHttpServlet ,!!!!!!!!!!!!!真正的运行类型是其子类
            LhHttpServlet lhHttpServlet =
                    LhTomcatV3.servletMapping.get(servletName);
            // 3 调用service方法 通过oop的动态绑定 调用运行类型的doGet(),doPost()
            if(lhHttpServlet!=null){
                lhHttpServlet.service(lhRequest, lhResponse);
            }else {
                //uri是瞎写的 不存在 没有这个servlet 返回404信息
                String resp=LhResponse.respHeader+"<h1>404 not Foud</h1>";
                OutputStream outputStream = lhResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

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

在这里插入图片描述
在这里插入图片描述

最终展示

浏览器输入:
http://localhost:8080/lhServletV3Demo01?a=1&b=2
在这里插入图片描述
http://localhost:8080/lhServletV88
在这里插入图片描述

在这里插入图片描述

三 、源码以及视频讲解

链接:
百度网盘下载
–来自百度网盘超级会员V4的分享

视频讲解得私聊我拿,不方便传播


总结

手动实现 Tomcat 底层机制+ 自己设计 Servlet。模拟Tomcat底层实现并能调用我们自己设计的Servlet实现一个简单的小案例。

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写一个Spring的源码实现是一个非常复杂的任务,需要对Spring的核心原理和设计思想有深入的理解。以下是一个简单的Spring源码实现的示例,仅供参考。 首先,我们需要创建一个核心的容器类来管理Bean的生命周期和依赖注入。这个容器类需要实现BeanFactory接口,提供getBean()方法来获取Bean对象。 ```java public interface BeanFactory { Object getBean(String name); } ``` 接下来,我们需要实现一个简单的Bean定义类,用于描述Bean的属性和依赖关系。 ```java public class BeanDefinition { private Class<?> beanClass; private Map<String, Object> properties = new HashMap<>(); private List<PropertyValue> propertyValues = new ArrayList<>(); public void setBeanClass(Class<?> beanClass) { this.beanClass = beanClass; } public Class<?> getBeanClass() { return beanClass; } public void addProperty(String name, Object value) { properties.put(name, value); } public Object getProperty(String name) { return properties.get(name); } public List<PropertyValue> getPropertyValues() { return propertyValues; } public void addPropertyValue(PropertyValue propertyValue) { propertyValues.add(propertyValue); } } public class PropertyValue { private final String name; private final Object value; public PropertyValue(String name, Object value) { this.name = name; this.value = value; } public String getName() { return name; } public Object getValue() { return value; } } ``` 然后,我们需要实现一个简单的BeanFactory实现类,用于创建和管理Bean对象。这个实现类需要读取Bean定义信息,创建Bean实例,并将其保存在一个Map中。 ```java public class SimpleBeanFactory implements BeanFactory { private final Map<String, BeanDefinition> beanDefinitions = new HashMap<>(); private final Map<String, Object> beans = new HashMap<>(); public SimpleBeanFactory(String configLocation) { loadBeanDefinitions(configLocation); createBeans(); } private void loadBeanDefinitions(String configLocation) { // 从配置文件中读取Bean定义信息 } private void createBeans() { for (String beanName : beanDefinitions.keySet()) { createBean(beanName); } } private Object createBean(String beanName) { BeanDefinition beanDefinition = beanDefinitions.get(beanName); Class<?> beanClass = beanDefinition.getBeanClass(); Object bean = createInstance(beanClass); applyPropertyValues(bean, beanDefinition.getPropertyValues()); beans.put(beanName, bean); return bean; } private Object createInstance(Class<?> beanClass) { try { return beanClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } private void applyPropertyValues(Object bean, List<PropertyValue> propertyValues) { for (PropertyValue propertyValue : propertyValues) { String propertyName = propertyValue.getName(); Object value = propertyValue.getValue(); setPropertyValue(bean, propertyName, value); } } private void setPropertyValue(Object bean, String propertyName, Object value) { try { BeanUtils.setProperty(bean, propertyName, value); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } @Override public Object getBean(String name) { return beans.get(name); } } ``` 最后,我们需要实现一个简单的测试类来验证我们的实现是否正确。 ```java public class SimpleBeanFactoryTest { @Test public void testGetBean() { BeanFactory beanFactory = new SimpleBeanFactory("classpath:beans.xml"); TestBean testBean = (TestBean) beanFactory.getBean("testBean"); assertNotNull(testBean); assertNotNull(testBean.getDependency()); } } ``` 这只是一个简单的Spring源码实现示例,实际上Spring的源码实现要复杂得多,涉及到很多高级特性和设计模式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值