Tomcat中Servlet的创建初始化体系结构及工作
Tomcat 中 Servlet 的创建初始化体系结构及工作
一个 Web 应用对应一个 Context 容器。在 Tomcat 中,Context 容器是管理 Servlet 的核心,也是 Servlet 运行时的容器。当添加一个 Web 应用时,Tomcat 会创建一个 StandardContext 容器,并为其设置必要的参数。其中,url 和 path 分别代表该应用在 Tomcat 中的访问路径和实际的物理路径。
Web 应用的初始化工作
Tomcat 中 Web 应用的初始化工作主要在 ContextConfig 的 configureStart 方法中实现。初始化的核心任务是解析 web.xml 文件,该文件描述了 Web 应用的关键信息,是应用的入口。
解析过程如下:
- 查找全局配置:Tomcat 首先查找
globalWebXml,该文件位于org/apache/catalina/startup/NO_DEFAULT_XML或conf/web.xml中的任一个。 - 查找宿主配置:接着查找
hostWebXml,即应用的配置文件(例如examples/WEB-INF/web.xml)。 - 解析与保存:
web.xml文件的各个配置项会被解析成相应的属性,保存在WebXml对象中。 - 设置容器属性:随后将
WebXml对象中的属性设置到 Context 容器中,包括创建 Servlet 对象、Filter、Listener 等。
最后,Servlet 会被包装成 StandardWrapper 并作为子容器添加到 Context 中。因此,Context 容器才是真正运行 Servlet 的容器,其配置属性由应用的 web.xml 指定。
创建 Servlet 对象
如果 Servlet 的 load-on-startup 配置项大于 0,那么在 Context 容器启动时就会被实例化。前文提到,在解析配置文件时会读取默认的 globalWebXml。在 conf 下的 web.xml 文件中定义了一些默认配置项,其中定义了两个 Servlet:
org.apache.catalina.servlets.DefaultServletorg.apache.jasper.servlet.JspServlet
它们的 load-on-startup 分别是 1 和 3。这意味着当 Tomcat 启动时,这两个 Servlet 会被优先启动。JspServlet 主要用于将 JSP 页面转换成 Servlet,它会拦截所有以 .jsp 为后缀的文件。
创建 Servlet 实例的方法是从 Wrapper.loadServlet 开始的。loadServlet 方法的主要任务是获取 servletClass,然后将其交给 InstanceManager 去创建一个基于 servletClass.class 的对象。
初始化 Servlet
由于 Servlet 在 Web 初始化时已经包装在 StandardWrapper 中,因此 Servlet 的初始化是在 StandardWrapper 的 initServlet 方法中进行的。该方法逻辑简单,主要是调用 Servlet 的 init 方法,同时将包装了 StandardWrapper 对象的 StandardWrapperFacade 作为 ServletConfig 传给 Servlet。
如果该 Servlet 关联的是一个 JSP 文件,那么前面初始化的就是 JspServlet。接下来会模拟一次简单请求,调用这个 JSP 文件,以便将其编译为 class 文件,并初始化这个 class。
Servlet 体系结构
Java Web 应用是基于 Servlet 规范运转的。下面介绍一下 Servlet 的体系结构。
顶层类关联

从上图可以看出,Servlet 规范主要基于几个核心类运转。与 Servlet 主动关联的是三个类,分别是 ServletConfig、ServletRequest 和 ServletResponse。这三个类都是通过容器传递给 Servlet 的:
- ServletConfig:在 Servlet 初始化时传给 Servlet,主要用于获取 Servlet 的配置属性,这些属性可能在 Servlet 运行时被用到。
- ServletRequest 和 ServletResponse:在请求到达调用 Servlet 时传递过来。它们代表了请求和响应的具体对象,通常作为运输工具来传递交互结果。
Servlet 的运行模式是一个典型的“握手型的交互式”运行模式。所谓“握手型的交互式”,是指两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随交易过程直到交易完成。交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常是一个配置类。
- 交易场景由
ServletContext来描述。 - 定制的参数集合由
ServletConfig来描述。
容器中的类关系
下图是 ServletConfig 和 ServletContext 在 Tomcat 容器中的类关系图。

上图可以看出,StandardWrapper 和 StandardWrapperFacade 都实现了 ServletConfig 接口,而 StandardWrapperFacade 是 StandardWrapper 的门面类。因此,传给 Servlet 的是 StandardWrapperFacade 对象。这个类能够保证从 StandardWrapper 中拿到 ServletConfig 所规定的数据,而又不把 ServletConfig 不关心的数据暴露给 Servlet。
同样,ServletContext 也与 ServletConfig 有类似的结构。Servlet 中能拿到的 ServletContext 的实际对象也是 ApplicationContextFacade 对象。ApplicationContextFacade 同样保证 ServletContext 只能从容器中拿到它该拿的数据。它们都起到了对数据的封装作用,使用的都是门面设计模式(Facade Pattern)。
通过 ServletContext 可以拿到 Context 容器中的一些必要信息,比如应用的工作路径、容器支持的 Servlet 最小版本等。
Request 相关类结构
我们在创建自己的 Servlet 类时,通常使用的是 HttpServletRequest 和 HttpServletResponse,它们继承了 ServletRequest 和 ServletResponse。为何 Context 容器传过来的 ServletRequest、ServletResponse 可以被转化为 HttpServletRequest 和 HttpServletResponse 呢?

Tomcat 一接收到请求,首先将会创建 org.apache.coyote.Request 和 org.apache.coyote.Response。这两个类是 Tomcat 内部使用的描述一次请求和相应信息的类,它们是轻量级的类。作用是在服务器接收到请求后,经过简单解析将这个请求快速地分配给后续线程去处理,所以它们的对象很小,很容易被 JVM 回收。
接下来,当交给一个用户线程去处理这个请求时,又创建 org.apache.catalina.connector.Request 和 org.apache.catalina.connector.Response 对象。这两个对象一直穿越整个 Servlet 容器,直到要传给 Servlet。传给 Servlet 的是 Request 和 Response 的门面类 RequestFacade 和 ResponseFacade。这里使用门面模式与前面一样,都是基于同样的目的——封装容器中的数据。
一次请求对应的 Request 和 Response 的类转化如下图所示:

Servlet 如何工作
当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname:port/contextpath/servletpath。
hostname和port:用来与服务器建立 TCP 连接。- 后面的 URL:用来选择服务器中那个子容器服务用户的请求。
在 Tomcat 7.0 中,这种映射工作用一个专门的类来完成,即 org.apache.tomcat.util.http.mapper。这个类保存了 Tomcat 的 Container 容器中的所有子容器的信息。当 org.apache.catalina.connector.Request 类进入 Container 容器之前,mapper 将会根据这次请求的 hostname 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中。因此,当 Request 进入 Container 容器之前,它要访问哪个子容器就已经确定了。
当 Request 请求到达了最终的 Wrapper 容器后,此时还需要真正的到达最终的 Servlet,这里还需要一些步骤:
- 必须执行 Filter 链。
- 通知你在
web.xml中定义的 Listener。
接下来就要执行 Servlet 的 service 方法了。通常情况下,我们自己定义的 Servlet 并不是直接去实现 javax.servlet.Servlet 接口,而是去继承更简单的 HttpServlet 类或者 GenericServlet 类,我们可以有选择地覆盖相应方法去实现我们要完成的工作。
Servlet 的确已经能够帮我们完成所有的工作了,但是现在的 Web 应用很少有直接将交互全部页面都用 Servlet 来实现,而是采用更加高效的 MVC 框架 来实现。这些 MVC 框架基本的原理都是将所有的请求都映射到一个 Servlet,然后去实现 service 方法,这个方法也就是 MVC 框架的入口。
当 Servlet 从 Servlet 容器中移除时,也就表明该 Servlet 的生命周期结束了。这时 Servlet 的 destroy 方法将被调用,做一些扫尾工作。
说明:本文内容基于 Tomcat 7.0 版本架构整理,部分类名与内部实现机制在不同版本(如 Tomcat 8/9/10)中可能有所调整,仅供参考学习 Servlet 容器基本原理。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。