Tomcat源码分析之 doGet方法(一)

欢迎点击「算法与编程之美」↑关注我们!

本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列博客。

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
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


        resp.getWriter().write("hello");
    }

}

2) web.xml 中添加servlet配置。


    helloServlet
    HelloServlet



    helloServlet
    /hello

3) 测试运行

浏览器地址栏输入:http://localhost:8080/hello

最终页面显示结果:hello

4) 进入调试分析阶段。

在 HelloServlet 的 doGet()方法前加上断点,点击调试按钮,进入调试阶段,开始源码分析。

3 分析流程

点击调试按钮,开始分析流程。

首先我们来看一下 doGet()方法的执行堆栈信息。

  1. at HelloServlet.doGet(HelloServlet.java:17)
  2. at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
  3. at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
  4. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  5. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  6. at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
  7. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  8. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  9. at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
  10. at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
  11. at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:475)
  12. at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
  13. at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
  14. at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
  15. at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
  16. at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
  17. at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:498)
  18. at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
  19. at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:796)
  20. at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1368)
  21. at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  22. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  23. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  24. at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  25. at java.lang.Thread.run(Thread.java:748)

从上面我们可以看到一共有25条堆栈信息,调用非常复杂,但这25条信息也并非是程序开始的地方,后面我们会带大家一起来寻找程序开始的地方。

我们要想搞明白 doGet()方法背后发生了什么,其实就是要弄明白这些堆栈信息,所以目前我们看到的就有25条信息,后面将一步步的带大家来了解。

由于分析内容较多, 我们将分几次博客来介绍,如果您对本系列博客感兴趣,欢迎关注微信公众号“算法与编程之美”,及时了解更多信息。

首先对这些堆栈信息做一些简单的介绍,

HelloServlet.doGet(HelloServlet.java:17)

表示 HelloServlet 类的 doGet()方法,代码行数为17.

3.1 HelloServlet.doGet

 


@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


    resp.getWriter().write("hello");  //17
}

这里面的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 interface Servlet {

        public void init(ServletConfig config) throws ServletException;

        public ServletConfig getServletConfig();

        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

        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,
        java.io.Serializable

我们可以得出 HelloServlet 是一个符合 JavaEE 规范的 Servlet,因此能够处理Http 请求,按照上述介绍的理论,当Http 请求'/hello'到达服务器时,将调用 HelloServlet 的 service 方法对其进行处理。

HelloServlet中并没有 service 方法,该方法位于其父类 HttpServlet 中,其定义如下所示:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest)req;
        response = (HttpServletResponse)res;
    } catch (ClassCastException var6) {
        throw new ServletException("non-HTTP request or response");
    }

    this.service(request, response);
}

从上述代码可以看到,将ServletRequest强制转化为 HttpServletRequest,将 ServletResponse 强制转化为 HttpServletResponse,然后再交给另外一个 service()方法处理。

为什么要做这种转化呢?为什么不直接处理 ServletRequest 和 ServletResponse?

 欢迎大家留言,说说您的看法。

做完这种类型转化后,交给另一个 service 方法处理。

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {

// ...
               doGet(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {



// ...
}

以上的代码大家应该都非常容易理解,首先获得 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 的相关知识点,欢迎大家持续关注。

欢迎持续关注“算法与编程之美”微信公众号,了解更多。

Tomcat源码分析之 doGet方法(一)_第1张图片

 

 

你可能感兴趣的:(Tomcat源码分析之 doGet方法(一))