注:在网上看到一篇文章--《Servlet工作原理》,整理并做了一些笔记
1. 了解 Servlet 容器
首先,要从servlet 容器开始。servlet容器,故名思议,就是装载和管理Servlet的服务端程序。借用一个前辈的解释:Servlet与Servlet容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力。
下图是Tomcat容器模型:
可以看出 Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程, Context 容器如何运行将直接影响 Servlet 的工作方式。
2. Servlet 容器启动过程
Tomcat7 支持嵌入式功能,增加了一个启动类 org.apache.catalina.startup.Tomcat。创建一个实例对象并调用 start 方法就可以很容易启动 Tomcat,还可以通过这个对象来增加和修改 Tomcat 的配置参数。下面我们就利用这个 Tomcat 类来管理新增的一个 Context 容器,我们就选择 Tomcat7 自带的 examples Web 工程,并看看它是如何加到这个 Context 容器中的。
# 创建一个 Tomcat 实例
Tomcat tomcat = getTomcatInstance();
File appDir = new File(getBuildDirectory(), "webapps/examples");
# 新增一个 Web 应用
tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath());
# 启动 Tomcat
tomcat.start();
# 调用其中的一个 HelloWorldExample Servlet,看有没有正确返回预期的数据。
ByteChunk res = getUrl("http://localhost:" + getPort() +
"/examples/servlets/servlet/HelloWorldExample"); assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);
一个 Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器(StandardContext是Context的标准实现),并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径。其中最重要的一个配置是 ContextConfig,这个类将会负责整个 Web 应用配置的解析工作。最后将这个 Context 容器加到父容器 Host 中。
接下去将会调用 Tomcat 的 start 方法启动 Tomcat,Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口,它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会由它去通知已经注册的观察者(Listener)。
当 Context 容器初始化状态设为 init 时,添加在 Contex 容器的 Listener 将会被调用。ContextConfig 继承了 LifecycleListener 接口,它是在调用 addWebapp 时被加入到 StandardContext 容器中。ContextConfig 类会负责整个 Web 应用的配置文件的解析工作。
ContextConfig 的 init 方法将会主要完成以下工作:
1. 创建用于解析 xml 配置文件的 contextDigester 对象
2. 读取默认 context.xml 配置文件,如果存在解析它
3. 读取默认 Host 配置文件,如果存在解析它
4. 读取默认 Context 自身的配置文件,如果存在解析它
5. 设置 Context 的 DocBase
ContextConfig 的 init 方法完成后,Context 容器的会执行 startInternal 方法,这个方法启动逻辑比较复杂,主要包括如下几个部分:
1. 创建读取资源文件的对象
2. 创建 ClassLoader 对象
3. 设置应用的工作目录
4. 启动相关的辅助类如:logger、realm、resources 等
5. 修改启动状态,通知感兴趣的观察者(Web 应用的配置)
6. 子容器的初始化
7. 获取 ServletContext 并设置必要的参数
8. 初始化“load on startup”的 Servlet