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

简介: 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 目标

本系列博客源码分析的目标是深入了解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(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)

23我们打算一起来分析,因为他们都在 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

 

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

目录
相关文章
|
5天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
21 1
|
2月前
|
监控 网络协议 应用服务中间件
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
本文详细解析了Tomcat架构中复杂的`Connector`组件。作为客户端与服务器间沟通的桥梁,`Connector`负责接收请求、封装为`Request`和`Response`对象,并传递给`Container`处理。文章通过四个关键问题逐步剖析了`Connector`的工作原理,并深入探讨了其构造方法、`init()`与`start()`方法。通过分析`ProtocolHandler`、`Endpoint`等核心组件,揭示了`Connector`初始化及启动的全过程。本文适合希望深入了解Tomcat内部机制的读者。欢迎关注并点赞,持续更新中。如有问题,可搜索【码上遇见你】交流。
【Tomcat源码分析】从零开始理解 HTTP 请求处理 (第一篇)
|
1月前
apache+tomcat配置多站点集群的方法
apache+tomcat配置多站点集群的方法
33 4
|
2月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
2月前
|
前端开发 Java 应用服务中间件
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
本文详细介绍了Java类加载机制及其在Tomcat中的应用。首先回顾了Java默认的类加载器,包括启动类加载器、扩展类加载器和应用程序类加载器,并解释了双亲委派模型的工作原理及其重要性。接着,文章分析了Tomcat为何不能使用默认类加载机制,因为它需要解决多个应用程序共存时的类库版本冲突、资源共享、类库隔离及JSP文件热更新等问题。最后,详细展示了Tomcat独特的类加载器设计,包括Common、Catalina、Shared、WebApp和Jsp类加载器,确保了系统的稳定性和安全性。通过这种设计,Tomcat实现了不同应用程序间的类库隔离与共享,同时支持JSP文件的热插拔。
【Tomcat源码分析 】"深入探索:Tomcat 类加载机制揭秘"
|
2月前
|
设计模式 应用服务中间件 容器
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
本文深入剖析了Tomcat中的Pipeline和Valve组件。Valve作为请求处理链中的核心组件,通过接口定义了关键方法;ValveBase为其基类,提供了通用实现。Pipeline则作为Valve容器,通过首尾相连的Valve链完成业务处理。StandardPipeline实现了Pipeline接口,提供了详细的Valve管理逻辑。通过对代码的详细分析,揭示了模板方法模式和责任链模式的应用,展示了系统的扩展性和模块化设计。
【Tomcat源码分析】Pipeline 与 Valve 的秘密花园
|
2月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
3月前
|
网络协议 Java 应用服务中间件
Tomcat源码分析 (一)----- 手撕Java Web服务器需要准备哪些工作
本文探讨了后端开发中Web服务器的重要性,特别是Tomcat框架的地位与作用。通过解析Tomcat的内部机制,文章引导读者理解其复杂性,并提出了一种实践方式——手工构建简易Web服务器,以此加深对Web服务器运作原理的认识。文章还详细介绍了HTTP协议的工作流程,包括请求与响应的具体格式,并通过Socket编程在Java中的应用实例,展示了客户端与服务器间的数据交换过程。最后,通过一个简单的Java Web服务器实现案例,说明了如何处理HTTP请求及响应,强调虽然构建基本的Web服务器相对直接,但诸如Tomcat这样的成熟框架提供了更为丰富和必要的功能。
|
3月前
|
Ubuntu Java 应用服务中间件
在Ubuntu 16.04上安装Apache Tomcat 8的方法
在Ubuntu 16.04上安装Apache Tomcat 8的方法
35 0