Servlet是 JavaWeb 开发中最常使用的一个接口,尤其是这个接口中的 doGet()和 doPost()方法。我们在做 web 开发的时候,经常会自定义一个 Servlet 如 HelloServlet,并且让这个类继承 HttpServelt,接着重写 doGet()方法就可以快速实现我们自己的请求服务。 那么 doGet()方法的背后到底发生了什么?有些同学可能会说这个问题很简单啊,就是HttpServlet 做了一次封装会判断 HTTP 请求的类型,如果是 get 请求就调用 doGet()方法,如果是 post 请求就调用 doPost()方法。 我们想要的并非这种简单的回答,而是探究这背后的背后究竟发生了什么? HelloServlet ->HttpServlet->ApplicationFilterChain -> WsFilter -> StandardWrapperValve ->StandardContextValve -> StandardHostValve -> StandardEngineValve ->CoyoteAdapter -> Http11Processor -> NioEndpoint -> ThreadPoolExecutor-> Worker -> TaskThread -> Thread -> Catalina ->Bootstrap.main() 这才是最终我们想要得到的答案,从 doGet 方法开始,逐步的探究它开始的地方,最终这个开始的地方在什么地方结束呢?答案无疑是 Tomcat 程序启动的入口 main 函数。只有完成了这样的一个历程,我们才能说我们彻底明白了 doGet()方法,彻底明白了这背后到底发送了什么。 通过本系列博客的阅读,您将彻底的了解 doGet()方法背后发生了什么,从源码的角度深入的理解 Tomcat 的实现机制,Tomcat 中各核心组件是如何协同工作的,同时也会学习到 WEB 服务器设计思路。 1 目标 本系列博客源码分析的目标是深入了解Tomcat中doGet方法的实现机制。本次源码分析的目标是了解 Servlet。 2 分析方法 首先编写测试代码,然后利用 Intellij Idea 的堆栈窗口、线程窗口以及单步调试功能,逐步的分析其实现思路。 前期准备工作如下: 1) 编写 HelloServlet类。public class HelloServlet extends HttpServlet { @Override } <servlet> 3)测试运行 浏览器地址栏输入:http://localhost:8080/hello 最终页面显示结果:hello4)进入调试分析阶段。 在 HelloServlet 的 doGet()方法前加上断点,点击调试按钮,进入调试阶段,开始源码分析。 3 分析流程 点击调试按钮,开始分析流程。 首先我们来看一下 doGet()方法的执行堆栈信息。
从上面我们可以看到一共有25条堆栈信息,调用非常复杂,但这25条信息也并非是程序开始的地方,后面我们会带大家一起来寻找程序开始的地方。 我们要想搞明白 doGet()方法背后发生了什么,其实就是要弄明白这些堆栈信息,所以目前我们看到的就有25条信息,后面将一步步的带大家来了解。 由于分析内容较多,我们将分几次博客来介绍,如果您对本系列博客感兴趣,欢迎关注微信公众号“算法与编程之美”,及时了解更多信息。 首先对这些堆栈信息做一些简单的介绍, HelloServlet.doGet(HelloServlet.java:17) 表示HelloServlet 类的 doGet()方法,代码行数为17. 3.1 HelloServlet.doGet @Override 这里面的17行就是我们打断点的地方,也是我们用户编写程序开始的地方。功能很简单就是向HTTP 响应中写入"hello"字符串。 3.2 javax.servlet.http.HttpServlet.service 2. at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) 3. at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) 2和3我们打算一起来分析,因为他们都在 HttpServlet 里面。在具体分析源码之前,我们先介绍一点关于 Servlet 的基本知识。 在学习Servlet 基础知识的时候,我们都知道,Servelt 是一段服务器端的程序,专门用来处理来自客户端请求的,处理完成后返回一个响应。大多数情况下这个请求是 Http 请求,这个响应是 Http响应,并且在Http 响应中包含了 HTML 代码。 Servlet是一个接口,其定义如下:
public void init(ServletConfigconfig) throws ServletException; public ServletConfiggetServletConfig(); public void service(ServletRequest req,ServletResponse res) public String getServletInfo(); public void destroy(); } 这个接口定义非常简单,主要方法介绍如下: init方法表示 Tomcat 服务器刚加载这个 Servlet 时执行的代码。 service方法表示这个 Servlet 在处理请求时执行的代码。 destroy 方法表示当不再使用这个 Servelt 销毁时执行的代码。 所以我们看到 Servlet 是有生命周期的,刚开始诞生的时候调用 init 方法,期间服务请求时,调用 Service 方法,最后销毁时调用 destroy 方法。 这里面尤其需要注意的是 service()方法的两个形参类型是 ServeltRequest和 ServletResponse。 由于我们在 web.xml 中配置了将访问路径为'/hello'的请求交给 HelloServlet 来处理,此外由 HelloServlet 的如下的继承关系: - HelloServlet 继承了HttpServlet public class HelloServlet extends HttpServlet - HttpServlet继承了 GenericServlet public abstract class HttpServlet extends GenericServlet - GenericServlet 实现了 Servlet 接口 public abstract class GenericServlet implements Servlet, ServletConfig, 我们可以得出 HelloServlet 是一个符合 JavaEE 规范的 Servlet,因此能够处理Http 请求,按照上述介绍的理论,当Http 请求'/hello'到达服务器时,将调用 HelloServlet 的 service 方法对其进行处理。 HelloServlet中并没有 service 方法,该方法位于其父类 HttpServlet 中,其定义如下所示: public void service(ServletRequest req, ServletResponse res) throws ServletException,IOException { 从上述代码可以看到,将ServletRequest强制转化为 HttpServletRequest,将 ServletResponse 强制转化为 HttpServletResponse,然后再交给另外一个 service()方法处理。 为什么要做这种转化呢?为什么不直接处理 ServletRequest和 ServletResponse? 欢迎大家留言,说说您的看法。 做完这种类型转化后,交给另一个 service 方法处理。 protected void service(HttpServletRequest req, HttpServletResponse resp) //... //... 以上的代码大家应该都非常容易理解,首先获得 Http 请求方法的类型,然后根据不同的类型去调用不同的方法,如方法类型为 get 则调用 doGet()方法。由于在子类 HelloServlet 实现了 doGet()方法,因此最终执行的是上面我们写的代码。 通过§3.1 和§3.2两节的分析我们知道,当一个请求达到 Servlet 的时候,首先会将这个请求转化为HttpServletRequest,这个响应转化为 HttpServletResponse,然后得到 Http 请求的方法类型,最后根据不同的方法类型调用不同的方法来处理。 4 总结 本文是《Tomcat 源码分析之 doGet方法》的第一篇文章,主要介绍了源码分析的目标以及主要任务有哪些,并对Servlet知识点做了非常细致的介绍,帮助大家更好的了解 Servlet,以及为什么用户自定义的 Servlet 需要继承 HttpServlet。 下一讲我们将介绍4、5、6、7、8,重点介绍 ApplicationFilterChain 的相关知识点,欢迎大家持续关注。 其他精彩文章: |
|