分享

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

 算法与编程之美 2020-08-08

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 目标

本系列博客源码分析的目标是深入了解TomcatdoGet方法的实现机制。本次源码分析的目标是了解 Servlet

2 分析方法

首先编写测试代码,然后利用 Intellij Idea 堆栈窗口、线程窗口以及单步调试功能,逐步的分析其实现思路。

前期准备工作如下:

1) 编写 HelloServlet类。

public class HelloServlet extends HttpServlet {

    @Override
   
protected void doGet(HttpServletRequestreq, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write(
"hello");//断点位置

    }
}

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

<servlet>
    <
servlet-name>helloServlet</servlet-name>
    <
servlet-class>HelloServlet</servlet-class>
</
servlet>

<
servlet-mapping>
    <
servlet-name>helloServlet</servlet-name>
    <
url-pattern>/hello</url-pattern>
</
servlet-mapping>

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(HttpServletRequestreq, 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)

23我们打算一起来分析,因为他们都在 HttpServlet 里面。在具体分析源码之前,我们先介绍一点关于 Servlet 的基本知识。

在学习Servlet 基础知识的时候,我们都知道,Servelt 是一段服务器端的程序,专门用来处理来自客户端请求的,处理完成后返回一个响应。大多数情况下这个请求是 Http 请求,这个响应是 Http响应,并且在Http 响应中包含了 HTML 代码。

Servlet是一个接口,其定义如下:


public interface Servlet {

        public void init(ServletConfigconfig) throws ServletException;

        public ServletConfiggetServletConfig();

        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 requestor 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);

    } e
lse 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

下一讲我们将介绍45678,重点介绍 ApplicationFilterChain 的相关知识点,欢迎大家持续关注。

其他精彩文章:

50行代码实现简单的网站服务器

50行代码实现网站服务器 2

50行代码实现网站服务器 3

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多