重拾后端 (一)——Servlet 原理

本贴最后更新于 2570 天前,其中的信息可能已经沧海桑田

由于在两年前曾经接触过一段时间的 J2EE,但那个时候只会一些简单的配置,了解了一些基本的东西,大三的时候才发现这些都是皮毛,于是想深入的学习整个框架,由此写下这一系列的分享心得,知其然更要知其所以然。

下面进入正文:

1.servlet 是什么?

首先我们进入官方网站看看详细信息。找到 javax.servlet 的包,我们发现其实 servlet 包里面就是一些接口,找到 servlet 核心接口,里面定义是这样说的 A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.意思是说 servlet 是一个运行在 web 服务器中的小程序,用来接收和响应来自 web 客户端的请求,使用 HTTP 进行通信。这里的 web 服务器就可以有很多了,如常见的 tomcat,jetty 等。

2.servlet 容器又是什么?

servlet 和 servlet 容器是彼此依存的,但是又相互发展,servlet,jsp 等都是运行在 servlet 容器内的,而 servlet 容器又是运行在 web 服务器的(tomcat,jetty),我们可以看看 tomcat 容器模型

可以看出 tomcat 容器分为四个等级真正管理 servlet 的是 context 容器,其实我们可以在 tomcat 的目录中 tomcat/config/server.xml 中发现这样的标签

<Context docBase="D:\Tomcat 8.0\webapps\TomcatTest" path="/TomcatTest" reloadable="true" source="org.eclipse.jst.j2ee.server:TomcatTest"/>
<Context docBase="D:\Tomcat 8.0\webapps\GitTest" path="/GitTest" reloadable="true" source="org.eclipse.jst.j2ee.server:GitTest"/>  

可以看出一个 servlet 容器可以有多个 context,也就意味可以有多个应用,每个应用又互不干扰。

3.Servlet 容器的启动过程

我们可以看看 tomcat 源码中有这样一个启动类可以用 java 来手动启动一个 tomcat 实例,并且配置相关信息,然后添加 webapp 应用,这一系列工作相当复杂,当好在我们可以不用这么麻烦,直接在 ide 既可以启动 tomcat,tomcat 容器就会按照流程对 web 应用进行初始化等一系列工作。

4.Web 应用的初始化

web 应用的初始化工作室在 ContextConfig 的 configureStart 返回中实现的,主要用来解析 web.xml.也就是 web 程序的入口,tomcat 首先会找到 globalWebXml,存在于 cof.web.xml 中,接下来会寻找 hostWebXml,然后寻找应用的配置文件 web.xml,通过解析保存在 WebXml 对象中,最后 tomcat 会将 WebXml 对象中的属性设置到 Context 容器中,这里就包括了 Servlet 对象、filter 过滤器、监听器等,但是最后返回的并不是 servlet 对象,而是经过包装后的 ServletWrapper 对象。这也就解释了为什么 tomcat 容器模型有四层。

5.创建 servlet 实例

当 Context 容器启动是,它会读取 conf/web.xml 下的内容,这个是所有 web 应用程序的根,里面定义了很多默认配置项,其实当我们的项目只有一个 index.jsp 没有任何的 servlet 的时候,运行起来可以直接访问 index.jsp,这是因为在默认配置项中有这样一句话

<!-- ================== Built In Servlet Definitions ==================== -->
 <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
<servlet>
        <servlet-name>jsp</servlet-name>
        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
        <init-param>
            <param-name>fork</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>xpoweredBy</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
    </servlet>

这其中有两个类,DefaultServlet 和 JspServlet,他们的载入顺序分别为 1 和 3,意味着当 tomcat 启动的时候他们就启动了,其实这两个类都继承了 httpservlet,而创建 servlet 实例是由 Wrapper.loadServlet 开始的,首先它会获得 servletClass,然后把它交给 InstanceManager 去创建基于 servletClass.class 的对象,如果这个对象配置了 jsp-file 的话,那么这个 servletClass 对象就是在上面的 JspServlet。

6.servlet 的初始化

在 StandardWrapper 中有个 initServlet 方法,它就是来调用 servlet 的 init 方法,同时把包装了 StandardWrapper 对象的 StandardWrappperFacade 作为 ServletConfig 对象传给 servlet。如果该 servlet 关联的是 jsp 文件,那么初始化的就是 jspServlet,接下来就会模拟以此请求,调用这个 jsp,并编译它,然后初始化这个类。

有关 servlet 初始化工作非常复杂,中间有非常多的过程,我们可以仔细查看源码文档。

7.Request 和 Response 对象剖析

Servlet 中定义了两个对象,ServletRequest 和 ServletResponse 对象,这两个对象分别是 HttpServletRequest 和 HttpServletRequest 的父类,但是为何 Context 容器传过来的 ServletRequest 和 ServletResponse 对象能被转化成

HttpServletRequest 和 HttpServletRequest 呢?解释如下:

当 tomcat 接收到一个请求时候,会创建 org.apache.coyote.Request 和 org.apache.coyote.Response 两个类,用来描述请求和相应的信息,经过简单处理后会分发给后续线程处理,所以他们的对象很小,容易被 jvm 回收,后续线程会创建 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象,这两个对象一直存在于整个 servlet 容器,直到传给目标 Servlet,但是目标接收的类型却是 RequestFacade 和 ResponseFacade,这两个叫做门面类,相当于两个童子,当书信要来的时候是这两个童子负责处理,目的是为了封装数据。

8.Servlet 是如何处理请求的

通常当我们发送一个请求的时候如 http://localhost:8080/TomcatTest/login,容器是如何找到 LoginServlet 这个类来处理的呢?其实 tomcat 中有一个 mapper 类,专门用来保存 tomcat 中的 container 容器中的子容器信息,当 org.apache.catalina.connector.Request 类进去 Container 容器之前,Mapper 将会根据这次请求的 hostname,contextpath 把这些信息设置到 Request 的 mappingData 属性中去,最后再去匹配相应的 Servlet 处理器。

到此为止 Servlet 已经可以帮我们完成所有工作了,但是现在的 web 应用很少直接将交互的全部页面交给 servlet 去实现,而是采用更加高效的 mvc 框架去实现,这些框架的基本原理就是把所有的请求都映射到一个 servlet 中,然后去实现 service 方法,比如后面要学习的 SpringMVC。

9.总结

本次学习主要回顾并深入的了解了 Servlet,通过阅读源码,官方文档让我们对 servlet 有了更深层次的了解

10.附录

有关本章的测试代码在我的 github 地址源代码

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    944 引用 • 1459 回帖 • 17 关注
  • Servlet
    21 引用 • 29 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • leap

    简单了解下 servlet 规范。会有帮助。