【JavaWeb学习笔记】10 - 手写Tomcat底层,Maven的初步使用

一、Maven

1.Maven示意图

类似Java访问数据库

2.创建Maven案例演示

配置阿里镜像

找到setting目录

但一开始配置不存在该文件

需要去Maven主目录下的conf拿到settings拷贝到上述目录

拷贝到admin/.m2后打开该settings

在<mirrors>内输入镜像地址

<mirror>

<id>alimaven</id>

<name>aliyun maven</name>

<url>https://maven.aliyun.com/nexus/content/groups/publichttps://maven.aliyun.com/repository/publichttps://maven.aliyun.com/nexus/content/groups/public</url>

<mirrorOf>central</mirrorOf>

</mirror>

配置pom.xml文件

XML 复制代码
    </dependency>
    <!--引入servlet.jar包-->
    <!--
    1.入servlet-api.jar ,为J开发servlet
    2. dependency 标签是表示引入-一个包
    3. groupId包的公司/ 组织/开发团队/个人信息javax. servlet
    4. artifactId :项目名javax .servlet-api
    5. version 版本
    6. scope 表示引入的包的作用范围
    7. provided: 表示tomcat 本身有jar包,这里你引入的jar包,在编译,测试有效
    但是在打包的时候不要带上这个jar包
    8.下载的包在你指定的目录:C:\Users\Administrator\.m2\repository
    9.可以去修改我们要下载包的位置->
    10.我们可以去指定maven仓库,即配置maven镜像C:\Users\Administratorl.m2\settings.xml

    -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

3.实现计算器效果

创建Tomcat的时候不要使用xxx_war包而要使用explore的

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
    <form action="/yhtomcat/calServlet" method="post">
        num1:<input type="text" name="num1"><br/>
        num2:<input type="text" name="num2"><br/>
        <input type="submit" value="submit">
    </form>
</body>
</html>
java 复制代码
@WebServlet(name = "CalServlet",urlPatterns = "/calServlet")
public class CalServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String strnum1 = request.getParameter("num1");
        String strnum2 = request.getParameter("num2");
        int num1 = 0;
        int num2 = 0;
        int sum = -1;
        try {
            num1 = Integer.parseInt(strnum1);
            num2 = Integer.parseInt(strnum2);
            System.out.println("res = " + num1 + num2);
            sum = num1 + num2;
        } catch (NumberFormatException e) {
            System.out.println("form wrong , continue");
        }
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        if (!(sum == -1)) {
            writer.print("<h1> res = " + sum + "</h1>");

        }else{
            writer.print("<h1> wrong date please try again!!  </h1>");
        }
        writer.flush();
        writer.close();
    }


    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request,response);
    }
}

二、Tomcat架构分析

我们的目标:不用Tomcat,不用系统提供的Servlet,

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

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

模拟Tomcat底层机制

一、编写自己Tomcat

1.基于socket开发服务端流程

  1. ServerSocket

在服务端监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回Socket对象

  1. Socket

表示服务端和客户端/浏览器间的连接,通过Socket可以得到InputStream和OutputStream流对象。

java 复制代码
public class YhTomcatV1 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("======yhtomcat 在8080端口监听");
        while (!serverSocket.isClosed()){
            //等待连接
            //如果有连接来,就创建一个socket
            //这socket就是服务端和浏觉器端的连接/通道
            Socket socket = serverSocket.accept();

            //先接受浏览器发来的数据
            //字节流
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader
                    (new InputStreamReader(inputStream,"utf-8"));
            String mes = null;
            System.out.println("=====接受到浏览器发送的数据======");
            while ((mes = bufferedReader.readLine()) != null){
                if(mes.length() == 0){//读到空字符串
                    break;
                }
                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 + "<h1>hi, 这是模拟Tomcat</h1>";

            System.out.println("========我们的tomcat 给浏览器会送的数据======");
            System.out.println(resp);
            outputStream.write(resp.getBytes());//将resp字符串以byte[] 方式返回

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

        }
    }
}

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

BIO线程模型介绍

需求分析

浏览器请求http:/ /localhost:8080,服务端返回hi , hspedu,后台hsptomcat使用BIO线程模型,支持多线程=>对前面的开发模式进行改造

一个持有线程的对象

java 复制代码
public class YhRequestHandler extends Thread {
/*
 * 1. HspRequestHandler 对象是一个线程对象
 * 2. 处理一个http请求的
 */
    //定义Socket
    private Socket socket = null;

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

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
            //1.使用BIO线程模型,支持多线程
            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;
            // io - 网络 - 线程 - 反射 - 注解 - OOP [都会学会,也会学好]
            //
            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>hi this is ThreadServlet</h1>";
            System.out.println("========Yhtomcatv2返回的数据是=========");
            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();
                }
            }
        }

    }
}

Tomcat

java 复制代码
public class YhTomcatV2 {
    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线程基础
            YhRequestHandler hspRequestHandler =
                    new YhRequestHandler(socket);
            new Thread(hspRequestHandler).start();
        }
    }
}

**问题分析:**MyT omcat只是简单返回结果,没有和Servlet,web.xml关联

3.处理 Servlet

Request处理请求信息

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

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


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

    /**
     * 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
     */
    private void encapHttpRequest() {
        System.out.println("yhRequest 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 +
                '}';
    }
}

注意 这里不能关闭流 inputStream 和 socket关联

Response对象处理响应 持有socket

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

    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

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

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


}

设计Servlet规范类以及Servlet接口

java 复制代码
public interface YhServlet {
    void init() throws Exception;

    void service(YhRequest request, YhResponse response) throws IOException;

    void destroy();
}
java 复制代码
public abstract class YhHttpServlet implements YhServlet {
    @Override
    public void service(YhRequest request, YhResponse 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(YhRequest request, YhResponse response);
    public abstract void doPost(YhRequest request, YhResponse response);
}

YhCalServlet实现该Servlet并写自己的业务代码

java 复制代码
public class YhCalServlet extends YhHttpServlet {
    @Override
    public void doGet(YhRequest request, YhResponse response) throws IOException {
        doPost(request,response);
    }

    @Override
    public void doPost(YhRequest request, YhResponse response) throws IOException {
        String strnum1 = request.getParameter("num1");
        String strnum2 = request.getParameter("num2");
        int num1 = 0;
        int num2 = 0;
        int sum = -1;
        try {
            num1 = Integer.parseInt(strnum1);
            num2 = Integer.parseInt(strnum2);
            System.out.println("res = " + num1 + num2);
            sum = num1 + num2;
        } catch (NumberFormatException e) {
            System.out.println("form wrong , continue");
        }
        // response.setContentType("text/html;charset=utf-8"); response内已经做了
        OutputStream outputStream = response.getOutputStream();
        if (!(sum == -1)) {
            outputStream.write((YhResponse.respHeader + "<h1> res = " + sum + "</h1>").getBytes());

        }else{
            outputStream.write((YhResponse.respHeader + "<h1> wrong date please try again!!  </h1>").getBytes());
        }
        outputStream.flush();
        outputStream.close();
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {

    }
}

4.使用反射去处理查找哪个calServlet

handler管理线程代码

java 复制代码
            //=====================通过反射来实现==========
            // 先说明一把实现思路->【停一下】 -> 如果你自己完成?10min
            // 1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            YhRequest yhRequest = new YhRequest(socket.getInputStream());
            YhResponse yhResponse = new YhResponse(socket.getOutputStream());
            String uri = yhRequest.getUri();
            String servletName = YhTomcatV3.servletUrlMapping.get(uri);
            if(servletName == null){
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 HspCalServlet
            YhHttpServlet yhHttpServlet =
                    YhTomcatV3.servletMapping.
                            get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost

            if (yhHttpServlet != null) {//得到
                yhHttpServlet.service(yhRequest, yhResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = YhResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = yhResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

模拟Tomcat利用反射和dom4j处理xml文件获取Servlet

java 复制代码
public class YhTomcatV3 {
    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例
    public static final ConcurrentHashMap<String, YhHttpServlet>
            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) throws MalformedURLException, DocumentException {
        YhTomcatV3 yhTomcatV3 = new YhTomcatV3();
        yhTomcatV3.init();
        //启动hsptomcat容器
        yhTomcatV3.startTomcatV3();
    }


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

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

    }

    //直接对两个容器进行初始化
    @Test
    public void init() throws MalformedURLException, DocumentException {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = YhTomcatV3.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(),
                            (YhHttpServlet) 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);
    }
}

二、课后作业

html 复制代码
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>计算器</title>
</head>
<body>
    <form action="/yhCalServlet" method="GET">
        num1:<input type="text" name="num1"><br/>
        num2:<input type="text" name="num2"><br/>
        <input type="submit" value="submit">
    </form>
</body>
</html>

在工具类内写方法判断 如果不是servlet 就判断是不是html

java 复制代码
public static String readHtml(String filename) {
        String path = com.yinhai.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();
    }

如果是html 就走该if体 将方法返回

java 复制代码
 // ====================新增业务逻辑===========
            //(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 = yhResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = yhResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }
            //===========================================
相关推荐
时光追逐者4 分钟前
MongoDB从入门到实战之MongoDB快速入门(附带学习路线图)
数据库·学习·mongodb
一弓虽9 分钟前
SpringBoot 学习
java·spring boot·后端·学习
晓数1 小时前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
我的golang之路果然有问题2 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
genggeng不会代码2 小时前
用于协同显著目标检测的小组协作学习 2021 GCoNet(总结)
学习
lwewan2 小时前
26考研——存储系统(3)
c语言·笔记·考研
搞机小能手2 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
nongcunqq3 小时前
爬虫练习 js 逆向
笔记·爬虫
jzshmyt3 小时前
大内存生产环境tomcat-jvm配置实践
java·jvm·tomcat
汐汐咯3 小时前
终端运行java出现???
笔记