Servlet 运行原理
在 Servlet 的代码中我们并没有写 main 方法, 那么对应的 doGet 代码是如何被调用的呢? 响应又是如何返回给浏览器的?
一、Tomcat在Servlet运行中的定位
我们自己的实现是在 Tomcat 基础上运行的。当浏览器给服务器发送请求的时候 , Tomcat 作为 HTTP 服务器 , 就可以 接收到这个请求。
详细的交互过程示意图:
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 中的数据按照一定的格式显示在浏览器的界面上。
二、Tomcat 是如何初始化/处理请求的?
1.Tomcat 初始化
Tomcat 的代码中内置了 main 方法 . 当我们启动 Tomcat 的时候 , 就是从 Tomcat 的 main 方法开
始执行的。
被 @WebServlet 注解修饰的类会在 Tomcat 启动的时候就被获取到 , 并集中管理。
Tomcat 通过 反射 这样的语法机制来创建被 @WebServlet 注解修饰的类的实例。
这些实例被创建完了之后 , 会调用其中的 init 方法进行初始化 . ( 这个方法是 HttpServlet 自带的 ,
我们自己写的类可以重写 init)。
这些实例被销毁之前 , 会调用其中的 destory 方法进行收尾工作 . ( 这个方法是 HttpServlet 自带的 ,
我们自己写的类可以重写 destory)。
Tomcat 内部也是通过 Socket API 进行网络通信。
Tomcat 为了能同时相应多个 HTTP 请求 , 采取了多线程的方式实现 . 因此 Servlet 是运行在 多线程
环境 下的。
2.Tomcat 处理请求
Tomcat 从 Socket 中读到的 HTTP 请求是一个字符串 , 然后会按照 HTTP 协议的格式解析成一个
HttpServletRequest 对象。
Tomcat 会根据 URL 中的 path 判定这个请求是请求一个静态资源还是动态资源 . 如果是静态资源 ,
直接找到对应的文件把文件的内容通过 Socket 返回 . 如果是动态资源 , 才会执行到 Servlet 的相关
逻辑。
Tomcat 会根据 URL 中的 Context Path 和 Servlet Path 确定要调用哪个 Servlet 实例的 service
方法。
通过 service 方法 , 就会进一步调用到我们之前写的 doGet 或者 doPost。
3.Servlet 的 service 方法
Servlet 的 service 方法内部会根据当前请求的方法 , 决定调用其中的某个 doXXX 方法。
在调用 doXXX 方法的时候, 就会触发 多态 机制, 从而执行到我们自己写的子类中的 doXXX 方法。
自己写的 类 , 继承自 HttpServlet 类 . 而 HttpServlet 又继承自 Servlet. 相当于自己写的类 就是 Servlet 的子类。 接下来 , 在 Tomcat 启动阶段 , Tomcat 已经根据注解的描述 , 创建了 自己写的类 的实例 , 然后把 这个实例放到了 Servlet 数组中。 后面我们根据请求的 URL 从数组中获取到了该 实例 , 但是我们是通过 Servlet ins 这样的父类引用来获取到该实例的。
最后 , 我们通过 ins.doGet() 这样的代码调用 doGet 的时候 , 正是 " 父类引用指向子类对象 ", 此
时就会触发多态机制 , 从而调用到我们之前写的类 中所实现的 doGet 方法。
等价代码 :
Servlet ins = new HelloServlet(); ins.doGet(req, resp);