Servlet 的主要工作
允许程序员注册一个类,在 Tomcat 收到的某个特定的 HTTP 请求的时候,执行这个类中的一些代码
帮助程序员解析 HTTP 请求,把 HTTP 请求从一个字符串解析成一个 HttpRequest 对象
帮助程序员构造 HTTP 响应,程序员只要给指定的 HttpResponse 对象填写一些属性字段,Servlet 就会自动的按照 HTTP 协议的方式构造出一个 HTTP 响应字符串,并通过 Socket 编写返回给客户端
一般的servlet
MySelfServlet =============extend====》HttpServlet
==============》GenericServlet
=============》Servlet
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter("user");
}
}
HttpServletRequest: 表示 HTTP 请求,Tomcat 按照 HTTP 请求的的格式把字符串格式的请求转换成了一个 HttpServletRequest 对象,通过这个对象就可以获取请求中的信息
HttpServletResponse: 表示 HTTP 响应,通过代码可以把响应的对象构造好,然后 Tomcat 将响应返回给浏览器
通过 doGet 的源码我们可以大致了解,它的作用是根据收到的请求通过响应返回一个 405 或者 400,那么我们可以重写这个方法,根据收到的请求执行自己的业务逻辑,把结果构造成响应对象
在 doGet 方法中,通过 HttpServletResponse
类的 getWriter()
方法往响应的 body 中写入文本格式数据
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String user = req.getParameter("user");
resp.getWriter().write("aa");
}
}
Servlet 运行原理
在 Servlet 的代码中,我们并没有写 main 方法,那么对应的 doGet 代码是如何被调用呢?响应又是如何返回给浏览器的呢?
Servlet 的架构
我们自己实现的 Servlet 是在 Tomcat 基础上运行的
当浏览器给服务器发送请求时,Tomcat 作为 HTTP 服务器,就可以接收到这个请求。Tomcat 的工作就是解析 HTTP 请求,并把请求交给 Servlet 的代码来进行进一步的处理。Servlet 的代码根据请求计算生成响应对象,Tomcat 再把这个响应对象构造成 HTTP 响应,返回给浏览器。并且 Servlet 的代码也经常会和数据库进行数据的传递。
Tomcat 的伪代码
下面通过 Tomcat 的伪代码的形式来描述 Tomcat 初始化和处理请求两部分核心逻辑
Tomcat 的初始化流程
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 方法进行初始化
这些实例被销毁之前,就会调用其中的 destory 方法进行收尾工作
Tomcat 内部也是通过 Socket API 进行网络通信
Tomcat 为了能够同时处理多个 HTTP 请求,采取了多线程的方式实现,因此 Servlet 是运行在多线程环境下的。
Tomcat 处理请求流程
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
// 直接使用 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
Tomcat 从 Socket 中读到的 HTTP 请求是一个字符串,然后 Tomcat 会按照 HTTP 协议的格式解析成一个 HttpServletRequest 对象
Tomcat 会根据 URL 中的 Path 判定这个请求是请求一个静态资源还是动态资源。如果是静态资源,直接找到对应的文件,把文件的内容通过 Socket 返回;如果是动态资源,才会执行到 Servlet 的相关逻辑
Tomcat 会根据 URL 中的 Context Path 和 Servlet Path 确定要调用哪个 Servlet 实例的 service 方法
通过 service 方法,就会进一步调用我们重写的 doGet 或者 doPost 方法等等
Servlet API 详解
对于 Servlet 主要介绍三个类,分别是 HttpServlet、HttpServletRequest 和 HttpServletResponse。
其中 HttpServletRequest 和 HttpServletResponse 是 Servlet 规范中规定的两个接口,HttpServlet 中并没有实现这两个接口的成员变量,它们只是 HttpServlet 的 service 和 doXXX 等方法的参数。这两个接口类的实例化是在 Servlet 容器中实现的。
HttpServlet
Servlet 的生命周期: Servlet 的生命周期就是 Servlet 对象从创建到销毁的过程,下面来介绍其生命周期的过程
Servlet 对象是由 Tomcat 来进行实例化的,并且在实例化完毕之后调用 init 方法(只调用一次)
Tomcat 对于收到的请求,都会通过对应的 Servlet 的 service 方法来进行处理(可以调用多次)
Tomcat 在结束之前,会调用 Servlet 的 destory 方法来进行回收资源(最多调用一次)
注意: init 和 service 能够保证在各自的合适时机被 Tomcat 调用,但是 destory 不一定,它是否能够被调用取决于 Tomcat 是如何结束的
如果直接杀死进程,那么就来不及调用 destory
如果通过 Tomcat 的管理端口(默认 8005)进行关闭,就能够调用 destory
HttpServletRequest
HttpServletResponse