承接 Servlet基础教程:https://blog.csdn.net/yyhgo_/article/details/128468738?spm=1001.2014.3001.5502
在 Servlet 的代码中我们并没有写 main 方法,那么对应的 doGet 代码是如何被调用的呢?响应又是如何返回给浏览器的?
我们自己的实现是在 Tomcat 基础上运行的。

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

更详细的交互过程可以参考下图:

下面的代码通过 “伪代码” 的形式描述了 Tomcat 初始化/处理请求 两部分核心逻辑。
所谓 “伪代码”, 并不是一些语法严谨, 功能完备的代码, 只是通过这种形式来大概表达某种逻辑 ~~
class Tomcat {// 用来存储所有的 Servlet 对象private List instanceList = new ArrayList<>();public void start() {// 根据约定,读取 WEB-INF/web.xml 配置文件;// 并解析被 @WebServlet 注解修饰的类// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. Class[] allServletClasses = ...;// 这里要做的的是实例化出所有的 Servlet 对象出来;for (Class cls : allServletClasses) {// 这里是利用 java 中的反射特性做的// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。Servlet ins = cls.newInstance();instanceList.add(ins);}// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;for (Servlet ins : instanceList) {ins.init();}// 利用我们之前学过的知识,启动一个 HTTP 服务器// 并用线程池的方式分别处理每一个 RequestServerSocket 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();}
}
小结:
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 页面,表示服务器内部错误}}
}
小结:
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);} ......}
}
小结:
理解此处的 多态:
我们前面自己写的 HelloServlet 类, 继承自 HttpServlet 类, 而 HttpServlet 又继承自 Servlet。相当于 HelloServlet 就是 Servlet 的子类。
接下来, 在 Tomcat 启动阶段, Tomcat 已经根据注解的描述, 创建了 HelloServlet 的实例, 然后把这个实例放到了 Servlet 数组中。
后面我们根据请求的 URL 从数组中获取到了该 HelloServlet 实例, 但是我们是通过 Servlet ins 这样的父类引用来获取到 HelloServlet 实例的。
最后, 我们通过 ins.doGet() 这样的代码调用 doGet 的时候, 正是 “父类引用指向子类对象”, 此时就会触发多态机制, 从而调用到我们之前在 HelloServlet 中所实现的 doGet 方法。
等价代码:
Servlet ins = new HelloServlet();
ins.doGet(req, resp);
Servlet生命周期描述的是Servlet创建到销毁的过程: