从servlet容器说起1 Servlet容器的启动过程2 Web 应用的初始化工作

要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪为弹而生,而弹又让枪有了杀伤力。虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产。从技术角度来说是为了解耦,通过标准化接口来相互协作。既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就从它们的接口说起。

Servlet 容器作为一个独立发展的标准化产品,目前种类很多,但是它们都有自己的市场定位,很难说谁优谁劣。以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 Servlet。Tomcat 本身也很复杂,我们只从 Servlet 与 Servlet 容器的接口部分开始介绍,关于 Tomcat 的详细介绍可以参考我的网上其他博文

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

Tomcat容器模型

从上图可以看出 Tomcat 的容器分为四个等级,真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程,在 Tomcat 的配置文件中可以很容易发现这一点,如下:

Context.xml配置文件内容

下面详细介绍 Tomcat 解析 Context 容器的过程,包括如何构建 Servlet

1 Servlet容器的启动过程

Tomcat7 开始支持嵌入式功能,增加了一个启动类 org.apache.catalina.startup.Tomcat 创建一个实例对象并调用 start 方法就可以很容易启动 Tomcat,我们还可以通过这个对象来增加和修改 Tomcat 的配置参数,如可以动态增加 Context、Servlet 等. 下面我们就利用这个 Tomcat 类来管理新增的一个 Context 容器,我们就选择 Tomcat7 自带的 examples Web 工程,并看看它是如何加到这个 Context 容器中的.

 Tomcat tomcat = getTomcatInstance(); 
 File appDir = new File(getBuildDirectory(), "webapps/examples"); 
 tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); 
 tomcat.start(); 
 ByteChunk res = getUrl("http://localhost:" + getPort() + 
               "/examples/servlets/servlet/HelloWorldExample"); 
 assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);

上段创建一个 Tomcat 实例并新增一个 Web 应用,然后启动 Tomcat 并调用其中的一个 HelloWorldExample Servlet,看有没有正确返回预期的数据。

Tomcat 的 addWebapp 方法的代码如下:

public Context addWebapp(Host host, String url, String path) { 
        silence(url); 
        Context ctx = new StandardContext(); 
        ctx.setPath( url ); 
        ctx.setDocBase(path); 
        if (defaultRealm == null) { 
            initSimpleAuth(); 
        } 
        ctx.setRealm(defaultRealm); 
        ctx.addLifecycleListener(new DefaultWebXmlListener()); 
        ContextConfig ctxCfg = new ContextConfig(); 
        ctx.addLifecycleListener(ctxCfg); 
        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); 
        if (host == null) { 
            getHost().addChild(ctx); 
        } else { 
            host.addChild(ctx); 
        } 
        return ctx; 
 }
  • 前面已经介绍了一个 Web 应用对应一个 Context 容器,也就是 Servlet 运行时的 Servlet 容器,
  • 添加一个 Web 应用时将会创建一个 StandardContext 容器,并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这个应用实际的物理路径,这个两个参数与清单 1 中的两个参数是一致的。
  • 其中最重要的一个配置是 ContextConfig,这个类将会负责整个 Web 应用配置的解析工作,后面将会详细介绍。
  • 最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用start 方法启动 Tomcat,它的启动逻辑基于观察者模式,所有的容器都会继承 Lifecycle 接口,它管理者容器的整个生命周期,所有容器的修改和状态的改变都会由它去通知已经注册的观察者(Listener)

Tomcat 主要类的启动时序图

上图描述了 Tomcat 启动过程中,主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启动过程

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

ContextConfig 的 init 方法将会主要完成以下工作

  • 创建用于解析 xml 配置文件的 contextDigester 对象
  • 读取默认 context.xml 配置文件,如果存在则解析它
  • 读取默认的 Host 配置文件,如果存在则解析它
  • 读取默认的 Context 自身的配置文件,如果存在则解析它
  • 设置 Context 的 DocBase

ContextConfig 的 init 方法完成后,Context 容器就会执行 startInternal 方法

  • 创建读取资源文件的对象
  • 创建 ClassLoader 对象
  • 设置应用的工作目录
  • 启动相关的辅助类如:logger、resources
  • 修改启动状态,通知感兴趣的观察者(Web 应用的配置)
  • 子容器的初始化
  • 获取 ServletContext 并设置必要的参数
  • 初始化“load on startup”的 Servlet

2 Web 应用的初始化工作

在 ContextConfig 的 configureStart 方法中实现的, 应用的初始化主要是解析 web.xml 文件,这个文件描述了Web 应用的关键信息,也是一个 Web 应用的入口。 Tomcat 首先会找 globalWebXml,这个文件的搜索路径是在 engine 的工作目录下寻找以下两个文件中的任一个 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。 接着会找 hostWebXml 这个文件可能会在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default, 接着寻找应用的配置文件 examples/WEB-INF/web.xml web.xml 文件中的各个配置项将会被解析成相应的属性保存在 WebXml 对象中。 如果当前应用支持 Servlet3.0,解析还将完成额外 9 项工作,这个额外的 9 项工作主要是为 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及对 annotations 的支持。

接下去将会将 WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等。 这段代码在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代码片段

for (ServletDef servlet : servlets.values()) { 
            Wrapper wrapper = context.createWrapper(); 
            String jspFile = servlet.getJspFile(); 
            if (jspFile != null) { 
                wrapper.setJspFile(jspFile); 
            } 
            if (servlet.getLoadOnStartup() != null) { 
                wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 
            } 
            if (servlet.getEnabled() != null) { 
                wrapper.setEnabled(servlet.getEnabled().booleanValue()); 
            } 
            wrapper.setName(servlet.getServletName()); 
            Map<String,String> params = servlet.getParameterMap(); 
            for (Entry<String, String> entry : params.entrySet()) { 
                wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
            } 
            wrapper.setRunAs(servlet.getRunAs()); 
            Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); 
            for (SecurityRoleRef roleRef : roleRefs) { 
                wrapper.addSecurityReference( 
                        roleRef.getName(), roleRef.getLink()); 
            } 
            wrapper.setServletClass(servlet.getServletClass()); 
            MultipartDef multipartdef = servlet.getMultipartDef(); 
            if (multipartdef != null) { 
                if (multipartdef.getMaxFileSize() != null && 
                        multipartdef.getMaxRequestSize()!= null && 
                        multipartdef.getFileSizeThreshold() != null) { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation(), 
                            Long.parseLong(multipartdef.getMaxFileSize()), 
                            Long.parseLong(multipartdef.getMaxRequestSize()), 
                            Integer.parseInt( 
                                    multipartdef.getFileSizeThreshold()))); 
                } else { 
                    wrapper.setMultipartConfigElement(new 
 MultipartConfigElement( 
                            multipartdef.getLocation())); 
                } 
            } 
            if (servlet.getAsyncSupported() != null) { 
                wrapper.setAsyncSupported( 
                        servlet.getAsyncSupported().booleanValue()); 
            } 
            context.addChild(wrapper); 
 }

这段代码描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper,为什么要将Servlet包装成StandardWrapper 而不直接是 Servlet 对象呢?StandardWrapper 是 Tomcat 容器的一部分,它具有容器的特征,而 Servlet 作为一个独立的 web 开发标准,不应该强耦合在 Tomcat 中

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏移动端开发

iOS 从实际出发理解多线程

前言 ----       多线程很多开发者多多少少相信也都有了解,以前有些东西理解的不是很透,慢慢的积累之后,这方面的东西也需要自己好好的总结一下。多线程从我...

2127
来自专栏恒思考

一个人的app后端-parse的安装与使用

mkdir -p ~/mongo ~/mongo/db cd ./mongo/ docker run -p 27017:27017 -v ~/mongo/db:...

2353
来自专栏MelonTeam专栏

一种下载管理方案的设计与实现

导语: 4G时代流量资费大幅下降,各种“WBQ”卡惊艳出世——在此背景下,下载对于移动端已不再是一种昂贵的高成本行为。同时EMMC、UFS等存储介质的发展也...

29111
来自专栏刘望舒

Android系统启动流程(四)Launcher启动过程与系统启动流程

前言 此前的文章我们学习了init进程、Zygote进程和SyetemServer进程的启动过程,这一篇文章我们就来学习Android系统启动流程的最后一步:L...

2358
来自专栏大内老A

[原创]WCF技术剖析之三:如何进行基于非HTTP的IIS服务寄宿

在上面一篇文章中,我们对不同版本的IIS,以及ASP.NET得的实现机制进行了详细而深入的分析。在介绍IIS7.0的时候,我们谈到,HTTP.SYS+W3SVC...

2075
来自专栏SDNLAB

OpenDaylight Lithium版本简单应用及流表操作指南

OpenDaylight(以下简写为ODL)的Lithium(锂)版本的最新版Lithium-SR2已经与2015年10月8日发布,具体详情可参考ODL官网。L...

4878
来自专栏魂祭心

原 以太坊-rpc原理及实现

3887
来自专栏开发技术

spring集成mybatis实现mysql读写分离

       在网站的用户达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈。幸运的是目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,...

1411
来自专栏Android开发指南

6.网络编程

33213
来自专栏Spark生态圈

[spark] Standalone模式下Master、WorKer启动流程

而Standalone 作为spark自带cluster manager,需要启动Master和Worker守护进程,本文将从源码角度解析两者的启动流程。Mas...

2542

扫码关注云+社区

领取腾讯云代金券