Servlet 工作流程解析

/ JavaWeb / 606浏览

如今大多数 JavaWeb 项目都使用高效的 MVC 框架来构建,而这些 MVC 框架的基本原理是将所有的请求都映射到一个 Servlet 上,然后去实现 service() 方法,这个方法就是 MVC 框架的入口。所以要理解 Servlet 的运行原理是非常重要的。

Servlet 容器

了解 Servlet 工作原理,就必须先要了解 Servlet容器, Servlet 与Servlet容器的关系有点像弓和箭的关系,弓是为箭而生的,而箭又让弓有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化的生产。从技术角度上来看,是为了解耦,通过标准化的接口来相互协作。

Servlet容器作为一个独立发展的标准化产品,目前种类很多,但是他们都有自己的市场定位,各有特点。例如Jetty、Tomcat等等。

在Tomcat容器等级中,Context 容器直接管理 Servlet 在容器中的包装类 Wrapper,所以Context容器如何运行将直接影响 Servlet 的工作方式。Tomcat容器模型图: alt

Tomcat容器模型

从图中可以看出 Tomcat 的容器分为4个等级,真正管理 Servlet 的容器是Context容器,也就是说一个Context容器对应一个web工程,在Tomcat的配置文件中可以很容易的发现这一点。

<Context path="/" docBase="p2p" debug="0" reloadable="true" />

Servlet 容器启动过程

Tomcat的启动逻辑是基于观察者模式设计的,所有的容器都会集成 Lifecycle 接口,它管理着容器的整个生命周期,所有容器的修改和状态的改变都会由它去通知已经注册的观察者(Listener)。

当Context容器初始化状态设为init时,添加到Context容器的Listener将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用 Tomcat.addWebapp() 时被加入到 StandardContext 容器中的。ContextConfig 类会负责整个web应用的配置文件的解析工作。

ContextConfig的init方法将会主要完成:

init方法完成后,Context容器就会执行startInternal方法,这个方法的启动逻辑比较复杂,主要包括:

Web应用的初始化工作

Web应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始化主要是解析 web.xml 文件,这个文件描述了一个web应用的关键信息,也是一个web应用的入口。

Tomcat首先会找 globalWebXml,接着会找hostWebXml,然后寻找应用的配置文件,也是我们熟知的 examples/WEB-INF/web.xml。web.xml文件中的各个配置项将会被解析成相应的属性保存在WebXml对象中。

接下来会将WebXml对象中的属性设置到Context容器中,这里包括创建Servlet对象、filter、listener 等。其中,解析 Servlet 部分会将Servlet 包装成 Context 容器中的 StandardWrapper,至于为什么要包装成 StandardWrapper 而不直接包装成Servlet对象呢?这里StandardWrapper 是tomcat容器中的一部分,它具有容器的特征(Servlet并没有),而 Servlet 作为一个独立的Web开发标准,不应该强耦合在Tomcat中。

⭐ 除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中外,其它所有的 web.xml 属性都被解析到 Context 容器中,所以说 Context 容器才是真正运行 Servlet 的 Servlet容器。一个Web应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解web工程里的 web.xml 到底起到什么作用了。

创建Servlet实例

创建Servlet对象

创建Servlet实例的方法是从 Wrapper.loadServlet 开始的。loadServlet方法主要完成的就是获取 servletClass,然后把它交给InstanceManager 去创建一个基于 servletClass.class 的对象。如果这个Servlet配置了jsp-file,那么这个 servletClass 就是在conf/web.xml 中定义的 org.apache.jasper.servlet.JspServlet 了。

如果 Servlet 的 load-on-startup 配置项大于0,那么在Context容器启动时就会被实例化,在 conf/web.xml 文件中定义了一些默认的配置项,其中定义了2个 Servlet,分别是 org.apache.catalina.servlets.DefaultServletorg.apache.catalina.servlets.JspServlet。他们的load-on-startup分别是 1 和 3,也就是当Tomcat启动时这两个Servlet就被启动。

初始化Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中,就是调用Servlet的init()方法,同时把包装了 StandardWrapper 对象的StandardWrapperFacade 作为 ServleConfig 传给Servlet。

如果一个Servlet关联的是一个JSP文件,那么前面初始化的就是JspServlet。

Servlet工作流程

当用户从浏览器向服务器发起一个请求,通常会包含如下信息:http://hostname:prot/contextpath/servletpath

内容说明
http基于http传输协议来传输数据
hostname、prot域名和端口来解析对应的服务器IP地址
contextpathWeb应用程序的根路径(对应Tomcat容器中的Context容器)
servletpath最终的路径才是负责处理用户的请求

服务器是如何根据这个URL来到达正确的 servlet 容器中的呢?

在Tomcat7中,是由一个专门的类来完成,这个类就是 org.apache.tomcat.util.http.mapper,这个类保存了Tomcat的Container容器中的所有子容器信息, org.apache.catalina.connector.Request 类在进入Container容器之前,Mapper将会根据这次请求的 hostname 和 contextpath 将 host 和 context 容器设置到 Request 的 mappingData 属性中,所以当 Request 进入 Container 容器之前,对于它要访问哪个子容器就已经确定了。

接下来就是 Filter 链,以及通知你在 web.xml 中定义的listener。

最后才执行Servlet中的 service() 方法。通常情况下我们自己定义的 servlet 并不直接去实现 javax.servlet.servlet 接口,而是去继承更简洁的 HttpServlet 类或者 GenericServlet 类,我们可以有选择地覆盖相应的方法去实现要完成的业务逻辑。

当 Servlet 从 Servlet 容器总移除时,也就表明该 Servlet 的生命周期结束了,这时 Servlet 的 destroy() 方法将被调用,做一些扫尾工作。

alt

配图:俄罗斯,弗拉基米尔金门