JavaEE-Servlet和Tomcat

Servlet运行原理

Servlet是属于上层建筑,下面的传输层,网络层,数据链路层…属于经济基础

在这里插入图片描述
HTTP Server(Tomcat)在调用Servlet,尤其是在处理请求的时候·~

当浏览器给服务器发送请求的时候, Tomcat 作为 HTTP 服务器, 就可以接收到这个请求.
HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作. 如下图所示

在这里插入图片描述
Tomcat其实是一个应用程序,运行在用户态的普通进程~
(Tomcat其实是一个Java进程)
用户写的代码(根据请求计算响应),通过Servlet和Tomcat进行交互~
Tomcat进一步和浏览器之间的网络传输,仍然走到是网络原理中的(封装和分用)
在这里插入图片描述

  1. 接收请求:
  • 用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求.
  • 这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit 流, 最终通过物理层的硬件设备转 换成光信号/电信号传输出去.
  • 这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达目标主机(这个过程也需 要网络层和数据链路层参与).
  • 服务器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 请求. 并交给 Tomcat 进程进行处理(根据端口号确定进程)
  • Tomcat 通过 Socket 读取到这个请求(一个字符串), 并按照 HTTP 请求的格式来解析这个请求, 根据 请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类. 再根据当前请 求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法. 此时我们的代码中的 doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息.
    1. 根据请求计算响应:
  • 在我们的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一 些信息, 来给 HttpServletResponse 对象设置一些属性. 例如状态码, header, body 等.
    1. 返回响应:
  • 我们的 doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置 好的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去.
  • 此时响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过 物理层硬件设备转换成光信号/电信号传输出去.
  • 这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达浏览器所在的主机(这个 过程也需要网络层和数据链路层参与).
  • 浏览器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 响应, 并交给浏览器处理.
  • 浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把 body 中的数据按照一定的格式显示在浏览器的界面上.
  • Tomcat伪代码

    伪代码,这个代码不能真正的编译运行(不必拘泥于语法的具体形式),主要是通过这个来体现逻辑~

    1.初始化

    让Tomcat先从指定的目录中找到所有要加载的Servlet类
    前面部署的时候,是把Servlet代码编码成了.class,然后打了war包,然后拷贝到了webapps里面~
    Tomcat就会从webapps里来找到哪些.class对应的Servlet类,并且需要进行加载~

    class Tomcat {
    // 用来存储所有的 Servlet 对象
    
     private List<Servlet> instanceList = new ArrayList<>();
     public void start() {
           // 根据约定,读取 WEB-INF/web.xml 配置文件;
    
            // 并解析被 @WebServlet 注解修饰的类
    
           
            // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
    
            Class<Servlet>[] allServletClasses = ...;
            
            // 这里要做的的是实例化出所有的 Servlet 对象出来;
    
            for (Class<Servlet> cls : allServletClasses) {
                // 这里是利用 java 中的反射特性做的
    
               // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
    
                // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
    
                // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
    
                
                Servlet ins = cls.newInstance();
                instanceList.add(ins);
           }
            
            // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
    
            for (Servlet ins : instanceList) {
                ins.init();
           }
            
            // 利用我们之前学过的知识,启动一个 HTTP 服务器
    
            // 并用线程池的方式分别处理每一个 Request
    
            ServerSocket serverSocket = new ServerSocket(8080);
            // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
    
            ExecuteService pool = Executors.newFixedThreadPool(100);
            
            while (true) {
                Socket socket = ServerSocket.accept();
                // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
    
                pool.execute(new Runnable() {
                   doHttpRequest(socket); 
               });
           }
            // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
    
            for (Servlet ins : instanceList) {
                ins.destroy();
           }
       }
        
        public static void main(String[] args) {
            new Tomcat().start();
       }
    }
    

    小结
    Tomcat 的代码中内置了 main 方法. 当我们启动 Tomcat 的时候, 就是从 Tomcat 的 main 方法开
    始执行的.

    被 @WebServlet 注解修饰的类会在 Tomcat 启动的时候就被获取到, 并集中管理.
    Tomcat 通过 反射 这样的语法机制来创建被 @WebServlet 注解修饰的类的实例.
    这些实例被创建完了之后, 会点调用其中的 init 方法进行初始化. (这个方法是 HttpServlet 自带的,

    我们自己写的类可以重写 init)

    这些实例被销毁之前, 会调用其中的 destory 方法进行收尾工作. (这个方法是 HttpServlet 自带的,

    我们自己写的类可以重写 destory)
    Tomcat 内部也是通过 Socket API 进行网络通信.
    Tomcat 为了能同时相应多个 HTTP 请求, 采取了多线程的方式实现. 因此 Servlet 是运行在 多线程
    环境下的.

    2. 处理请求

    class Tomcat {
        void doHttpRequest(Socket socket) {
            // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
    
            HttpServletRequest req = HttpServletRequest.parse(socket);
            HttpServletRequest resp = HttpServletRequest.build(socket);
            
            // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
    内容
    
            // 直接使用我们学习过的 IO 进行内容输出
    
            if (file.exists()) {
                // 返回静态内容
    
                return;
           }
            
            // 走到这里的逻辑都是动态内容了
    
            
            // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
    
            // 最终找到要处理本次请求的 Servlet 对象
    
            Servlet ins = findInstance(req.getURL());
            
            // 调用 Servlet 对象的 service 方法
    
            // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
    
            try {
           ins.service(req, resp); 
           } catch (Exception e) {
                // 返回 500 页面,表示服务器内部错误
    
           }
       }
    }
    

    Tomcat 的代码中内置了 main 方法. 当我们启动 Tomcat 的时候, 就是从 Tomcat 的 main 方法开
    始执行的.

    被 @WebServlet 注解修饰的类会在 Tomcat 启动的时候就被获取到, 并集中管理.
    Tomcat 通过 反射 这样的语法机制来创建被 @WebServlet 注解修饰的类的实例.

    // 用来存储所有的 Servlet 对象

    private List instanceList = new ArrayList<>();
    public void start() {
    // 根据约定,读取 WEB-INF/web.xml 配置文件;

        // 并解析被 @WebServlet 注解修饰的类
    
       
        // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. 
    
        Class<Servlet>[] allServletClasses = ...;
        
        // 这里要做的的是实例化出所有的 Servlet 对象出来;
    
        for (Class<Servlet> cls : allServletClasses) {
            // 这里是利用 java 中的反射特性做的
    
           // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
    
            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
    
            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
    
            
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
       }
        
        // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
    
        for (Servlet ins : instanceList) {
            ins.init();
       }
        
        // 利用我们之前学过的知识,启动一个 HTTP 服务器
    
        // 并用线程池的方式分别处理每一个 Request
    
        ServerSocket serverSocket = new ServerSocket(8080);
        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
    
        ExecuteService pool = Executors.newFixedThreadPool(100);
        
        while (true) {
            Socket socket = ServerSocket.accept();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
    
            pool.execute(new Runnable() {
               doHttpRequest(socket); 
           });
       }
        // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
    
        for (Servlet ins : instanceList) {
            ins.destroy();
       }
    

    }

    public static void main(String[] args) {
        new Tomcat().start();
    

    }
    }
    这些实例被创建完了之后, 会点调用其中的 init 方法进行初始化. (这个方法是 HttpServlet 自带的,

    我们自己写的类可以重写 init)

    这些实例被销毁之前, 会调用其中的 destory 方法进行收尾工作. (这个方法是 HttpServlet 自带的,

    我们自己写的类可以重写 destory)
    Tomcat 内部也是通过 Socket API 进行网络通信.
    Tomcat 为了能同时相应多个 HTTP 请求, 采取了多线程的方式实现. 因此 Servlet 是运行在 多线程
    环境 下的.

    3. service 方法的实现

    class Servlet {
        public void service(HttpServletRequest req, HttpServletResponse resp) {
            String method = req.getMethod();
            if (method.equals("GET")) {
                doGet(req, resp);
           } else if (method.equals("POST")) {
                doPost(req, resp);
           } else if (method.equals("PUT")) {
                doPut(req, resp);
           } else if (method.equals("DELETE")) {
                doDelete(req, resp);
           } 
           ......
       }
    }
    

    Servlet 的 service 方法内部会根据当前请求的方法, 决定调用其中的某个 doXXX 方法.

    在调用 doXXX 方法的时候, 就会触发 多态 机制, 从而执行到我们自己写的子类中的 doXXX 方法.

    Servlet API

    在讨论到上面这套流程过程中,涉及到了关于Servlet的关键方法,主要有三个。

    init:初始化阶段,对象创建好了之后,就会执行到,用户可以重写这个方法,来执行一些初始化逻辑。
    service:在处理请求阶段来调用,每次来个请求都要调用一次service
    destroy:退出主循环,tomcat结束之后就会调用,用来释放资源。
    在这里插入图片描述

    HttpServlet

    通过继承这个类,重写其中的方法,来被Tomcat执行的~
    在这里插入图片描述
    在这里插入图片描述
    注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例.

    代码示例: 处理 GET 请求
    创建 MethodServlet.java, 创建 doGet 方法

    @WebServlet("/method")
    
    public class MethodServlet extends HttpServlet {
        @Override
    
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    
    throws ServletException, IOException {
            resp.getWriter().write("GET response");
       }
    }
    

    创建 testMethod.html, 放到 webapp 目录中, 形如
    在这里插入图片描述
    一个 Servlet 程序中可以同时部署静态文件. 静态文件就放到 webapp 目录中即可.

    <button onclick="sendGet()">发送 GET 请求</button>
    <script>
    
        function sendGet() {
            ajax({
                method: 'GET',
                url: 'method',
                callback: function (body, status) {
                    console.log(body);
               }
           });
       }
        // 把之前封装的 ajax 函数拷贝过来
    
        function ajax(args) {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                // 0: 请求未初始化
    
                // 1: 服务器连接已建立
    
                // 2: 请求已接收
    
                // 3: 请求处理中
    
                // 4: 请求已完成,且响应已就绪
    
                if (xhr.readyState == 4) {
                    args.callback(xhr.responseText, xhr.status)
               }
           }
            xhr.open(args.method, args.url);
            if (args.contentType) {
                xhr.setRequestHeader('Content-type', args.contentType);
           }
    	if (args.body) {
                xhr.send(args.body);
           } else {
                xhr.send();
           }
       }
    
    </script>
    

    重新部署程序, 使用 URL http://127.0.0.1:8080/ServletHelloWorld/testMethod.html 访问页
    面.
    在这里插入图片描述
    点击 “发送 GET 请求” 按钮, 即可在控制台看到响应内容.
    在这里插入图片描述
    代码示例: 处理 POST 请求
    在 MethodServlet.java 中, 新增 doPost 方法.

    @Override
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws 
    
    ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("POST 响应");
    }
    

    在 testMethod.html 中, 新增一个按钮, 和对应的点击事件处理函数

    <button onclick="sendPost()">发送 POST 请求</button>
    <script>
    
        function sendPost() {
            ajax({
                method: 'POST',
                url: 'method',
                callback: function (body, status) {
                    console.log(body);
               }
           })
       }
    
    </script>
    

    重新部署程序, 使用 URL http://127.0.0.1:8080/ServletHelloWorld/testMethod.html 访问页
    面.
    在这里插入图片描述
    点击 “发送 POST 请求” 按钮, 可以在控制台中看到结果
    在这里插入图片描述

    HttpServletRequest

    当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成
    HttpServletRequest 对象.
    在这里插入图片描述
    通过这些方法可以获取到一个请求中的各个方面的信息.
    注意: 请求对象是服务器收到的内容, 不应该修改. 因此上面的方法也都只是 “读” 方法, 而不是 “写”

    方法.
    代码示例: 打印请求信息
    创建 ShowRequest 类

    @WebServlet("/showRequest")
    
    public class ShowRequest extends HttpServlet {
        @Override
    
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    
    throws ServletException, IOException {
            resp.setContentType("text/html; charset=utf-8");
            StringBuilder respBody = new StringBuilder();
            respBody.append(req.getProtocol());
            respBody.append("<br>");
            respBody.append(req.getMethod());
            respBody.append("<br>");
            respBody.append(req.getRequestURI());
            respBody.append("<br>");
            respBody.append(req.getContextPath());
            respBody.append("<br>");
            respBody.append(req.getQueryString());
            respBody.append("<br>");
            respBody.append("<h3>headers:</h3>");
            Enumeration<String> headerNames = req.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                respBody.append(headerName + " ");
                respBody.append(req.getHeader(headerName));
                respBody.append("<br>");
           }
            resp.getWriter().write(respBody.toString());
       }
    }
    

    部署程序.

    在浏览器通过 URL http://127.0.0.1:8080/ServletHelloWorld/showRequest 访问, 可以看到
    在这里插入图片描述
    代码示例: 获取 GET 请求中的参数

    GET 请求中的参数一般都是通过 query string 传递给服务器的. 形如

    https://v.bitedu.vip/personInf/student?userId=1111&classId=100

    此时浏览器通过 query string 给服务器传递了两个参数, userId 和 classId, 值分别是 1111 和 100

    在服务器端就可以通过 getParameter 来获取到参数的值.

    创建 GetParameter 类

    @WebServlet("/getParameter")
    
    public class GetParameter extends HttpServlet {
        @Override
    
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    
    throws ServletException, IOException {
            resp.setContentType("text/html; charset=utf-8");
            String userId = req.getParameter("userId");
            String classId = req.getParameter("classId");
            resp.getWriter().write("userId: " + userId + ", " + "classId: " + 
    
    classId);
       }
    }
    

    重新部署程序, 在浏览器中通过 http://127.0.0.1:8080/ServletHelloWorld/getParameter 访问,

    可以看到
    在这里插入图片描述
    当没有 query string的时候, getParameter 获取的值为 null.

    如果通过 http://127.0.0.1:8080/ServletHelloWorld/getParameter?
    userId=123&classId=456 访问, 可以看到
    在这里插入图片描述
    此时说明服务器已经获取到客户端传递过来的参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼王胖嘟嘟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值