【Servlet学习二】Servlet原理(Tomcat) ServletAPI

news2024/11/14 18:59:51

目录

🌟一、Servlet运行原理

🌈1、Servlet的执行原理(重点)

🌈2、Tomcat伪代码的简单理解

2.1  Tomcat初始化流程

2.2  Tomcat处理请求流程

2.3 Servlet 的 service 方法的实现

🌟二、Servlet API 详解

1、HttpServlet

2、HttpServletRequest

3、HttpServletResponse

面试题:请求转发与请求重定向的区别?


🌟一、Servlet运行原理

🌈1、Servlet的执行原理

1、问题:

        在Servlet的代码中我们并没有写main方法,那么对应的doGet方法是如何被调用的?响应又是如何返回给浏览器的?这就和Tomcat有关了。


2、Servlet的具体执行过程

我们自己的实现是在Tomcat中实现的。

        客户端发送请求——>到Tomcat的webServer——>Servlet管理器(多个)——>Servlet实例。

        总结起来,Servlet工作原理是:客户端发送请求到Web服务器,Web服务器把请求交给Servlet容器,Servlet容器根据请求的URI来确定应该由哪一个Servlet来处理请求,Servlet容器调用Servlet的init()、service()等方法,Servlet实例根据请求对象进行相应的业务逻辑处理,并把处理结果存储到响应对象中,最终Web服务器把响应返回给客户端。
        具体过程:

(1)接收请求

  • 用户在浏览器输入一个URL,此时浏览器就会构造出一个HTTP请求;
  • HTTP请求开始从应用层往下逐层封装数据(打包)得到一个二进制的bit流,最后通过物理层将数据传输给服务器端的物理层;
  • 服务器端的物理层接收到数据之后,开始从物理层往上逐层分用,层层解析数据(解析),最终还原出HTTP请求,并交给Tomcat进程进行处理(根据端口号确定进程)
  • Tomcat通过Socket读取到这个请求(一个字符串),并按照HTTP请求的格式来解析这个请求:根据请求中的Context Path确定一个webapp,再通过Servlet Path确定一个具体的类,再根据当前请求的方法(GET或者POST或其他)决定调用这个类的doGet或者doPost方法。此时我们的代码中的doGet或者doPost方法的第一个参数HttpServletRequest 就包含了这个 HTTP 请求的详细信息。

(2)根据请求计算响应

        在我们的doGet或者doPost执行完毕之后,就执行到了我们自己的代码。我们的代码会根据请求中的一些信息,来给HttpServletResponse对象设置一些属性:比如状态码,header,body等。

(3)返回响应

  • 等我们的doGet或者doPost执行结束之后,Tomcat就会自动将HttpServletResponse这个我们刚设置好的对象转化为一个符合HTTP协议的字符串,通过Socket将这个响应发送出去;
  • 然后响应数据在服务器的主机上又通过网络协议栈层层封装,得到一个二进制的bit流,通过物理层将数据传输出去;
  • 此时浏览器的物理层收到了响应数据,从下往上到应用层将数据进行分用,还原成HTTP响应,交给浏览器处理;
  • 浏览器通过Socket读到这个响应(一个字符串),按照HTTP响应的格式来解析这个响应,并将body中的数据按照一定的格式显示在浏览器的界面上。

 🌈2、Tomcat伪代码的简单理解

 2.1  Tomcat初始化流程

class Tomcat {
    //2、存储所有的Servlet对象
    private List<Servlet> instanceList = new ArrayList<>();

    //3、定义start方法
    public void start(){
        //(1)根据约定,读取WEB-INF/web.xml配置文件
        //(2)解析出@webServlet注解修饰的类
        //(3)假设这个数组中就包含了我们解析到的所有被@webServlet注解修饰的类
        Class<Servlet>[] allServletClasses = ...;

        //(4)实例化出所有的Servlet对象出来
        for(Class<Servlet> cls : allServletClasses){
            //注意:这里是利用java的反射做的,实际上还涉及到一个类的加载问题,因为我们的类字节码文件是按照约定的方式,全部在WEB-INF/classes文件夹下存放的,所以TOmcat内部实现了一个自定义的类加载器C(ClassLoader)来负责这部分工作
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
        }
        
        //(5)调用每个Servlet对象的init()方法,每个init()方法在对象的生命周期中只会调用一次
        for(Servlet ins : instanceList){
            ins.init();
        }

        //(6)启动一个HTTP服务器,并用线程池的方式分别处理每一个Request
        ServerSocket serverSocket = new ServerSocket(8080);
       //注意:实际上tomcat并不是用的固定线程池,这里只是为了说明情况

        while(true){
            Socket socket = ServerSocket.accpect();
            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
                doHttpRequest(socket);
            });
        }
        
        //(7)调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
        }
    }

    //1、main方法
    public static void main(String[] args) {
        new Tomcat().start();
    }

Tomcat初始化流程总结:

        内置main方法,启动start()——>获取@webServlet注解修饰的类——>通过反射获取@注解修饰的类的实例——>实例调用init()——>Tomcat使用多线程的方式实现多个Http请求的响应——>销毁实例:destory()

         Tomcat在启动的时候,自己有一个main方法,Servlet自己里面没有main方法,Tomcat在启动的时候,会在整个工程里面找所有加了注解@webServlet(“URL”)的类, 并将它识别为Servlet,调用init()方法初始化;而且会找到注解相应的路径,根据用户URL中的地址,解析出时哪一个Servlet,再执行它的doGet和doPost方法。

(1)Tomcat的代码中内置了main方法,当我们启动Tomcat之后,就是从Tomcat的main方法开始指定的;

(2)被@webServlet注解修饰的类会在Tomcat启动的时候就被获取到,并集中管理;

(3)Tomcat通过反射这种语法机制来创建被@webServlet注解修饰的类的实例;

(4)这些实例被创建完成之后,会调用其中的init方法进行初始化;

注意:这个init()方法在整个生命周期中只会执行一次。并且这个方法是HttpServlet自带的,我们自己写的类可以重写init;

(5)这些实例被销毁之前,会调用destory()方法;(同样这个类也是HttpServlet自带的,可以自己重写。)

(6)Tomcat内部也是通过Socket API进行网络通信的;

(7)Tomcat为了同时响应多个Http请求,采取了多线程的方式实现。因此Servlet是运行在多线程环境下的。

2.2  Tomcat处理请求流程

在2.1的初始化代码中,有一段用来处理请求的,我们来具体看一下这段代码的含义。

class Tomcat{

    //处理请求
    void doHttpRequest(Socket socket){
        //(1)进行HTTP协议的请求解析和响应构建
        HTtpServletRequest req = HttpServletRequest.parse(socket);
        HTtpServletRequest resp = HttpServletRequest.build(socket);
        //(2)判断URL对应的文件是否可以在我们的根路径上找到对应的文件,如果可以找到,就是静态内容,直接使用IO进行内容输出
        if(file.exists()){
            //返回静态内容
            return;
        }
        //(3)否则就是动态内容
        //按照URL——servlet-name —— Servlet独享的链条,最终找到要处理本次请求的Servlet对象
        Servlet ins = findInstance(req.getURL());
        
        //(4)调用Servlet对象的service方法,这里就会最终调用到我们最终写的HttpServlet的子类里面的方法了
        try {
            ins.service(req, resp);
        } catch (Exception e) {
        // 返回 500 页面,表示服务器内部错误
}
    }
}

Tomcat 处理请求流程小结:
(1)Tomcat从Socket中读取到的HTTP请求是一个字符串,因此会按照HTTP协议的格式解析成一个HttpServletRequest 对象;

 (2)Tomcat会根据URL的path判定这个请求是请求一个静态资源还是动态资源:如果是静态资源,直接找到对应的文件将文件中的内容通过Socket返回;如果 是动态资源,才会执行到Servlet的相关逻辑;

(3)Tomcat会根据URL中的Context Path和Servlet Path确定要调用哪个Servlet实例的service方法;

(4)通过Service方法,就会进一步调用到我们之前写的doGet或者doPost方法。

 2.3 Servlet 的 service 方法的实现

在2.2节doHttpRequest方法下:

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 方法的实现小结:

(1)Servlet的service方法内部会根据当前请求的方法来决定调用其中的某个doXXX方法;

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


问题:理解此处的多态

(1)我们前面【Servlet学习一】代码中的Demo1_HelloServlet类,继承自HttpServlet,HttpServlet又继承于Servlet。因此相当于Demo1_HelloServlet是Servlet的子类;

(2)接下来在Tomcat启动阶段,Tomcat就根据注解的描述,创建了Demo1_HelloServlet的实例,然后将实例放在了Servlet数组中;

(3)后面我们根据请求的URL从数组中获取到了Demo1_HelloServlet实例,但是我们是通过Servlet ins这样的父类引用来获取到Demo1_HelloServlet实例的;

(4)最后, 我们通过 ins.doGet() 这样的代码调用 doGet 的时候, 正是 "父类引用指向子类对象",此时就会触发多态机制,从而调用到我们之前在 Demo1_HelloServlet中所实现的 doGet 方法。

等价代码:

Servlet ins = new Demo1_HelloServlet();
ins.doGet(req, resp);

🌟二、Servlet API 详解

我们之前介绍过,实现的一个简单的Servlet的部分代码如下:

写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法。

有了HttpServlet才有后续的HttpServletRequest和HttpServletResponse,因此主要介绍这三部分。

1、HttpServlet

我们写 Servlet 代码的时候,首先第一步就是先创建类,继承自 HttpServlet,并重写其中的某些方法。核心方法:

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次
destory在 HttpServlet 实例不再使用的时候调用一次
service收到 HTTP 请求的时候调用
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/...收到其他请求的时候调用(由 service 方法调用)

实际开发过程中主要重写doGet与doPost方法,其他方法都很少使用。

2、HttpServletRequest

方法描述
String getProtocol()返回请求协议的名称和版本。
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该
请求的 URL 的一部分。
String getContextPath()返回指示请求上下文的请求 URI 部分。
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串。
Enumeration
getParameterNames()
返回一个 String 对象的枚举,包含在该请求中包含的参数的名
称。
String getParameter(String
name)
以字符串形式返回请求参数的值,或者如果参数不存在则返回
null。
String[]
getParameterValues(String
name)
返回一个字符串对象的数组,包含所有给定的请求参数的值,
如果参数不存在则返回 null。
Enumeration
getHeaderNames()
返回一个枚举,包含在该请求中包含的所有的头名。
String getHeader(String
name)
以字符串形式返回指定的请求头的值。
String
getCharacterEncoding()
返回请求主体中使用的字符编码的名称。
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null。
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果
长度未知则返回 -1。
InputStream
getInputStream()
用于读取请求的 body 内容. 返回一个 InputStream 对象

注意:请求对象是服务器收到的内容, 不应该修改。因此上面的方法也都只是 "读" 方法, 而不是 "写"方法。

3、HttpServletResponse

        Servlet 中的 doXXX 方法的目的就是根据请求计算得到响应, 然后把响应的数据设置到
HttpServletResponse 对象中。然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式,转成一个字符串, 并通过Socket 写回给浏览器。

方法描述
void setStatus(int sc)为该响应设置状态码。
void setHeader(String name,
String value)
设置一个带有给定的名称和值的 header. 如果 name 已经存在,
则覆盖旧的值.
void addHeader(String
name, String value)
添加一个带有给定的名称和值的 header. 如果 name 已经存在,
不覆盖旧的值, 并列添加新的键值对
void setContentType(String
type)
设置被发送到客户端的响应的内容类型。
void
setCharacterEncoding(String
charset)
设置被发送到客户端的响应的字符编码(MIME 字符集)例
如,UTF-8。
void sendRedirect(String
location)
使用指定的重定向位置 URL 发送临时重定向响应到客户端。
PrintWriter getWriter()用于往 body 中写入文本格式数据.
OutputStream
getOutputStream()
用于往 body 中写入二进制格式数据

注意: 响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序猿设置的。因此上面的方法都是 "写" 方法。注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前,否则可能设置失效。

面试题:请求转发与请求重定向的区别?

区别:

(1)定义不同

  •  请求转发(Forward):发生在服务器程序内部:当服务器端接收到一个客户端的请求之后,会先将请求转发给目标地址,然后再将目标地址返回的结果给客户端,而客户端对这是毫无感知的。这就好比,张三(客户端)找李四(服务器端)借钱,而李四没钱,于是李四又去王五那借钱,并把钱借给了张三,整个过程中张三只借了一次款,剩下的事情都是李四完成的,这就是请求转发。
  • 请求重定向(Redirect):请求重定向指的是服务器端在接收到客户端的请求之后,会给客户端返回一个临时响应头,这个临时响应头中记录了,客户端需要再次发送请求(重定向)的URL地址,客户端接收到地址之后,会将请求发送到新的地址上,这就是请求重定向。这就好像张三(客户端)找李四(服务器端)借钱,李四没钱,于是李四就告诉张三,“我没钱,你去王五那借“,于是张三又去王五家借到了钱,这就是请求重定向。

(2)请求方不同:请求转发是服务器端的行为,服务器端代替客户端发送请求,并将结果返回给客户端;而请求重定向是客户端的行为。

(3)数据共享不同:请求转发是服务器端实现的,因此整个交互过程中使用的都是同一个 Request 请求对象和一个 Response 响应对象,请求和返回的数据是共享的;而请求重定向客户端发送两次完全不同的请求,所以两次请求中的数据是不同的。

(4)最终 URL 地址不同:请求转发过程中 URL 地址是不变的,请求重定向浏览器会重新再发送一次请求,而非刚开始请求的地址,所以 URL 地址发生了改变。

(5)实现代码不同。

        演示请求重定向方法:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/redirectServlet")
public class Demo1_RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendRedirect("http://www.baidu.com");
    }
}

运行后获取网址在浏览器打开:


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

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

相关文章

SFP3012-ASEMI代理MHCHXM(海矽美)快恢复二极管SFP3012

编辑&#xff1a;ll SFP3012-ASEMI代理MHCHXM&#xff08;海矽美&#xff09;二极管SFP3012 型号&#xff1a;SFP3012 品牌&#xff1a;MHCHXM&#xff08;海矽美&#xff09; 封装&#xff1a;TO-247AB 恢复时间&#xff1a;≤65ns 正向电流&#xff1a;30A 反向耐压&a…

初学spring5(三)依赖注入(DI)

学习回顾&#xff1a;初学spring (五) 快速上手spring Dependency Injection 一、概念 依赖注入&#xff08;Dependency Injection,DI&#xff09;。依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 . 二、构造器…

SQL-每日一题【180.连续出现的数字】

题目 表&#xff1a;Logs 编写一个 SQL 查询&#xff0c;查找所有至少连续出现三次的数字。 返回的结果表中的数据可以按 任意顺序 排列。 查询结果格式如下面的例子所示&#xff1a; 示例 1: 解题思路 1.要查询至少连续出现三次的数字&#xff0c;则可以转化为&#xff08;…

软考A计划-系统集成项目管理工程师-项目整体管理-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

【C#】文件拖拽,获取文件路径

系列文章 【C#】编号生成器&#xff08;定义单号规则、固定字符、流水号、业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

vue2实现一个上边为搜索,下面为复选框选中后,右侧显示已选中组件

目录 vue2实现一个上边为搜索&#xff0c;下面为复选框选中后&#xff0c;右侧显示已选中组件component / ProjectSelectItem.vue使用组件效果 vue2实现一个上边为搜索&#xff0c;下面为复选框选中后&#xff0c;右侧显示已选中组件 component / ProjectSelectItem.vue <…

对于小米随手吸尘器各种问题的初始办法

本人在什么值得买发过&#xff0c;现在只不过是为了让账号看起来更丰盈一点&#xff0c;现在再发一次。 充不进去电/使用的时候短暂吸气&#xff0c;这两个问题&#xff0c;在上个月的时候我就已经发现了这个问题。 然后我就拆开了那个主机&#xff0c;就是那个保温杯一样的东…

SpringCloud(3) Ribbon负载均衡,负载均衡策略,自定义负载均衡

目录 1.Ribbon负载均衡流程2.Ribbon负载均衡策略3.自定义负载均衡策略如何实现4.总结 1.Ribbon负载均衡流程 假设我们有一台 order-service 订单服务&#xff0c;两台 user-service 用户服务&#xff0c;当订单服务需要调用用户服务获取用户信息的时候&#xff0c;应该怎么分配…

Windows下mysql 8.0.11 安装教程

http://www.jb51.net/article/140950.htm &#xff1a;此文章注意my.ini的扩展名 MySQL安装参考&#xff1a;mysql-8.0.11-winx64.zip在Windows中的安装配置-百度经验 MySQL卸载&#xff1a;https://blog.csdn.net/cxy_summer/article/details/70142322 下载最新的MySQL 1、…

经商必看的10本书籍推荐

1、《经理人参阅&#xff1a;商业模式与商业思维》 这本书被誉为商业模式的圣经。该书通过可视化工具&#xff0c;包括商业模式画布和价值主张画布&#xff0c;帮助企业家、创业者和经理人创造创新的商业模式。本书通过图表、图像和实例&#xff0c;详细讲解了商业模式的构建方…

智慧饮品未来发展方向:点赋科技AI智能饮品机器人的崛起

随着人们对健康饮品的追求和科技的不断创新&#xff0c;智慧饮品正在迅速崛起并成为未来饮品市场的热点。其中&#xff0c;AI智能饮品机器人作为一种创新的服务形式&#xff0c;将在未来发展中扮演重要角色。点赋科技将探讨智慧饮品市场的未来发展方向以及AI智能饮品机器人的潜…

URL(URI) 中的编码与乱码(下)--查询字符串(query string)中的编码

在上篇中, 初步谈论了 URL 中含有中文字符时的转义编码, 提到了所使用的编码是 utf-8. 不过你可能会有点疑问, 一定都是要用 utf-8 编码吗? 还是因为页面编码本身是 utf-8 的缘故呢? 毕竟在那个例子中, 页面的编码也恰好是 utf-8. 在 GBK 编码页面下的 URL 转义 这次, 将继…

Linux10.地址空间

1.几乎所有的语言&#xff0c;如果它有地址的概念&#xff0c;这个地址一定不是物理地址&#xff0c;是虚拟地址(线性地址)。 2.诸如网卡&#xff0c;硬盘和显卡等外设内部也具有寄存器。 3.在makefile中&#xff0c;gcc -o 文件A 文件1 文件2(…)可以写成 :gcc -o $ $^ 4.地…

程序员常用的几个效率小工具,可好用了!

目录 &#x1f5a5;️ Hexed.it &#x1f5bc;️ Carbon &#x1f5bc;️ Draw.net 今天给大家推荐几个平时工作学习中常用到的小工具&#xff0c;让你效率拉满&#xff01; &#x1f5a5;️ Hexed.it 在线地址&#xff1a;HexEd.it - Browser-based Online and Offline He…

增强深度学习与对抗训练对癫痫发作的鲁棒预测

标题&#xff1a;Augmenting Deep Learning with Adversarial Training for Robust Prediction of Epilepsy Seizures Abstract: 癫痫是一种慢性疾病&#xff0c;涉及异常的大脑活动&#xff0c;导致患者失去对意识或运动活动的控制。因此&#xff0c;在癫痫发作之前检测出癫…

Learn Mongodb DB数据库部署 ②

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

类加载的时机

类加载的时机 类的生命周期 类从被加载到虚拟机内存开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期包括以下 7 个阶段&#xff1a; 加载 验证 准备 解析 初始化 使用 卸载 验证、准备、解析 3 个阶段统称为连接。 加载、验证、准备、初始化和卸载这 5 个阶段的…

【MATLAB第49期】基于MATLAB的深度学习ResNet-18网络不平衡图像数据分类识别模型

【MATLAB第49期】基于MATLAB的深度学习ResNet-18网络不平衡图像数据分类识别模型 一、基本介绍 这篇文章展示了如何使用不平衡训练数据集对图像进行分类&#xff0c;其中每个类的图像数量在类之间不同。两种最流行的解决方案是down-sampling降采样和over-sampling过采样。 在…

力扣 -- 64.最小路径和

题目链接&#xff1a;64. 最小路径和 - 力扣&#xff08;LeetCode&#xff09; 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目的。 参考代码&#xff1a; class Solution { public:int minPathSum(vector<vector…

filezilla 刷新文件

ubuntu上的filezilla一直右键找不到文件刷新的选项&#xff0c;今天终于找到了 点击那个循环利用的按钮&#xff0c;就是刷新文件和文件夹&#xff0c;这个和mac上的操作有点不一致&#xff0c;很奇怪哈