Tomcat架构解析之Web应用加载(下篇)

四、StandardContext

    对于StandardHost和HostConfig来说,知识根据不同情况(部署描述文件、部署目录、WAR包)创建并且启动Context对象,并不包含具体的Web应用初始化以及启动工作,该部分工作由组件Context完成。

先给出Web容器相关的静态结构如下图所示:
在这里插入图片描述
    从上图可知,Tomcat提供的ServletContext实现类为ApplicationContext。但是,该类仅提供Tomcat服务器使用,Web应用使用的是其门面类ApplicationContextFacade。FilterConfig实现类为ApplicationFilterConfig,同时该类也负责Filter的实例化。FilterMap用于存储filter-mapping配置。NamingResources用于存储Web应用声明的命名服务(JNDI)。StandardContext通过servletMappings属性存储servlet-mapping配置。

接下来看一下StandardContext的启动过程:

  1. 发布正在启动的JMX通知,这样可以通过添加NotificationListener来监听Web应用的启动。
  2. 启动当前Context维护的JNDI资源。
  3. 初始化当前Context使用的WebResourceRoot并启动。WebResourceRoot维护了Web应用所有的资源集合(Class文件、Jar包以及其他资源文件),主要用于类加载和按照路径查找资源文件。
  4. 创建Web应用类加载器(WebappLoader)。WebappLoader继承自LifecycleMBeanBase,在其启动时创建Web应用类加载器(WebappClassLoader)。此外,该类还提供了background-Pocess,用于Context后台处理。当检测到Web应用的类文件、Jar包发生变更时,重新加载Context。
  5. 如果没有设置Cookie处理器,则创建默认的Rfc6265CookiePrcessor。
  6. 设置字符集映射(CharsetMapper),该映射主要用于根据Locale获取字符集编码。
  7. 初始化临时目录,默认为&CATALINA_BASE/work/<Engine名称>/<Host名称>/<Context名称>。
  8. Web应用的依赖检测,主要检测依赖扩展点完整性。
  9. 如果当前Context使用JNDI,则为其添加NamingContextListener。
  10. 启动Web应用类加载器(WebappLoader.start),此时才真正创建WebappClassLoader实例。
  11. 启动安全组件(Realm)。
  12. 发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件以完成Servlet的创建。
  13. 启动Context子节点(Wrapper)。
  14. 启动COntext维护的Pipeline 。
  15. 创建会话管理器。如果配置了集群组件,则由集群组件创建,负责使用标准的会话管理器(StandardManager)。在集群环境下,需要将会话管理器注册到集群组件。
  16. 将Context的Web资源集合添加到ServletContext属性,属性名称为org.appche.catalina.resources。
  17. 创建实例管理器(InstanceManager),用于创建对象实例,如Servlet、Filter等。
  18. 将Jar包扫描器(JarScanner)天机到ServletContext属性,属性名称为org.appche.tomcat.JarScanner。
  19. 合并ServletContext初始化参数和Context组件中的ApplicationParameter。合并原则:ApplicationParameter配置为可以覆盖,那么只有当ServletContext没有相关参数或者相关参数为空时添加;如果配置为不可覆盖,则强制添加,此时即使ServletContext配置了同名参数也不会生效。
  20. 启动添加到当前Context的SevletContainerInitializer。该类的实例具体由ContextConfig查找并添加,该类的主要用于以可编程方式添加Web应用的配置,如Servlet、Filter等。
  21. 实例化应用监听器(ApplicationListener),分为事件监听器以及生命周期监听器。这些监听器可以通过Context部署描述文件、可编程的方式或者Web.xml添加,并且触发ServletContextListener.contextInitialized。
  22. 检测未覆盖的HTTP方法的安全约束。
  23. 启动会话管理器。
  24. 实例化FilterConfig(ApplicationFilterConfig)、Filter,并且调用Filter.init初始化。
  25. 对于LoadOnstartup >= 0的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init进行初始化。
  26. 启动后台定时处理线程。只有backgroundProcessorDelay>0时启动,用于监控守护文件的变更等。当backgroundProcessorDelay<=0,表示Context的后台任务上级容器(Host)调度。
  27. 发布正在运行的JMX通知。
  28. 调用WebResourceRppt.gc()释放资源(WebResourceRoot加载资源时,为了提高性能会缓存某个信息,该方法用于清理这些资源,如关闭JAR文件)。
  29. 设置Context的状态,如果启动成功,设置为STARTING(其父类LifecycleBase会自动将状态转换为STARTED),否则设置为FAILED。

    通过上面的讲述,我们已经占到了StandardContext的整个启动过程,但是这部分工作并不包含如何解析Web.xml中的Servlet、请求映射、Filter等相关配置。这部分工作具体是由COntextConfig完成的。

五、ContextConfig

    Context创建时会默认添加一个生命周期监听器——ContextConfig。该监听器一共处理6类事件,但是仅讲解其中
与Context气动关系重大的3类:AFTER_INIT_EVENT、BEFORE_START+EVENT、CONFIGURE_START_EVENT,以便理解该类在Context启动中扮演的角色。

1.AFTER_INIT_EVENT事件

    严格意义上来讲,该事件属于Context初始化节点,他主要用于Context属性的配置工作。

    Context的创建可以有已下几个来源:

  • 在实例化Server时,解析server.xml文件中的Context元素创建。
  • 在HostConfig部署Web应用时,解析Web应用(目录或者WAR包)根目录下的EMTA-INF/context.xml文件创建。如果不存在该文件,则自动创建一个Context对象,仅设置path、docBase等少数几个属性。
  • 在Host部署Web应用时,解析$CATALINA_BASE/conf/<Engine名称>/<Host名称>下的Context部署描述文件创建。

    除了Context创建时的属性配置,将Tomcat提供的默认配置也一并添加到Context实例(如果Context没有显示的配置这些属性)这部分工作即由改时间完成,具体过程如下:
(1)如果Context的override属性为false(即使用默认配置):

  • 如果C存在conf/context.xml文件(Catalina容器级默认配置),那么解析该文件,更新当前Context实例属性;
  • 如果存在conf/<Engine名称>/<Host名称>/context.xml.default文件(Host级默认配置),那么解析该文件,更新当前Context实例属性。

(2)如果Context的configFile属性不为空,那么解析该文件,更新当前Context实例的属性。

    通过上面的执行顺序我们可以知道,Tomcat中的Context属性的优先级为:configFile、conf/<Engine名称>/<Host名称>/context.xml.default、conf/context.xml,即Web应用配置优先级最高,其次为Host配置,Catalina容器配置优先级最低。

2.BEFORE_START_EVENT事件
    该事件在Context启动之前触发,用于更新Context的docBase属性和解决Web目录锁的问题。

    更新Context的docBase属性主要是为了满足WAR部署的情况。当Web应用为一个WAR压缩包且需要解压部署(Host的unpackWAR为true,并且Context的unpackWAR为true)时,docBase属性指向的是解压后的文件夹目录,而非WAR包的路径。

    具体的处理过程如下:
(1)根据Host的appBase以及Context的docBase计算docBase的绝对路径。
(2)如果是docBase为一个WAR文件,且需要解压部署:

  • 解压WAR文件;
  • 将Context的docBase更新为解压后的路径(基于appBase的相对路径)。

(3)如果docBase为一个有效目录,而且存在与该目录同名的WAR包,同时需要解压部署,则重新解压WAR包。
(4)如果docBase为一个不存在的目录,但是存在与该目录同名的WAR包,同时需要解压部署:

  • 解压WAR文件;
  • 将Context的docBase更新为解压后的路径(基于appBase的相对路径)。

    当 Context的antiResourceLocking属性为true时,Tomcat会将当前的Web应用目录复制到临时文件夹下,以避免对原目录的资源加锁,具体过程如下:

(1) 根据Host的appBase以及Context的docBase计算docBase的绝对路径。
(2)计算临时文件夹中的Web应用根目录或WAR包名。

  • Web目录:${Context生命周期内的部署次数} - ${目录名}。
  • WAR包:${COntext生命周期内的部署次数} - ${WAR包名}。

(3)复制Web目录或者WAR包到临时目录。
(4)将Context的docBase更新为临时目录下的Web应用目录或者WAR包路径。

    通过上面的讲解,无论是AFTER_INIT_EVENT还是BEFORE_START_EVENT的处理,仍属于启动前的准备工作,以确保Context相关属性的准确性。而真正创建Wrapper的则是CONFIGURE_START_EVENT事件。

3.CONFIGURE_START_EVENT事件
    Context在启动子节点之前,触发了CONFIGURE_START_EVENT事件。ContextConfig正是通过该事件解析web.xml,创建Wrapper(Servlet)、Filter、ServletContextListener等一系列Web容器相关的对象,完成Web容器的初始化。

    先从整体上看一下ContextConfig在处理CONFIGURE_START_EVENT事件时做了哪些工作,然后再具体介绍web.xml的解析过程,该事件的主要工作内容如下:

  • 根据配置创建Wrapper(Servlet)、Filter、ServletContextListener等,完成Web容器的初始化。除了解析Web应用目录下的web.xml外,还包括Tomcat默认配置、web-fragment.xml、ServletContainerInitializer,以及相关XML文件的排序和合并。
  • 如果StandardContext的ignoreAnnotations为false,则解析应用程序注解配置,添加相关的JNDI资源引用。
  • 基于解析完的Web容器,检测Web应用部署描述中的安全角色名称,当发现使用了未定义的角色时,提示警告同时将未定义的角色添加到Context安全角色列表中。
  • 当Context需要进行安全认证但是没有指定具体的Authenticator时,根据服务器配置自动创建默认实例。

【1】Web容器初始化
    根据Sevlet规范,Web应用部署描述可来源于WEB-INF/web.xml、Web应用JAR包中的META-INF/web-fragment.xml和META-INF/services/javax.servlet.ServletContainerInitializer。

    其中META-INF/services/javax.serviceServletContainerInitializer文件中配置了所属AJR该接口的实现类,用于动态注册Servlet,这是Servlet规范基于SPI机制的可编程实现。

    除了Servlet规范中提到的部署描述方式,Tomcat还支持默认配置,以简化Web应用的配置工作。这些默认配置包括容器级别(conf/web.xml)和Host级别(conf/<Engine名称>/<Host名称>/web.xml.default)。Tomcat解析时确保Web应用中的配置优先级最高,其次为Host,最后为容器级。

    Tomcat初始化Web容器的过程如下:
(1)解析默认配置,生成WebXml对象(Tomcat使用对象表示web.xml的解析结果)。先解析容器级别配置,然后再解析Host级别。这样对于同名配置,Host级将覆盖容器级。为了便于后续过程描述,暂且称之为“默认WebXml”。为了提升性能,ContextConfig对默认WebXml进行了缓存,以避免重复解析。

(2)解析Web用的web.xml文件。如果StandardContext的altDDName不为空,则将该属性指向的文件作为web.xml,否则使用默认路径,即WEB-INF/web.xml。解析结果同样为WebXml对象(此时创建的对象为主WebXml,其他解析结果均需要合并到该对象上)。暂时将其称为“主WebXml”。

(3)扫描Web应用所有JAR包,如果包含META-INF/web-fragment.xml,则解析文件并创建WebXml对象。暂时将其称为“片段WebXml”。

(4)将web-fragment.xml创建的WebXml对象按照Servlet规范进行排序,同时将排序结果对应的JAR文件名列表设置到ServletContext属性中,属性名为javax.servlet.context.orderedLibs。该排序非常重要,因为这决定了Filter等的执行顺序。

(5)查找ServletContainerInitializer实现,并且创建实例,查找范围分为两个部分:

  • Web应用下的包:如果javax.servlet.context.orderdLibs不为空,仅搜索该属性包含的包,否则搜索WEB-INF/lib下的所有包。
  • 容器包:搜索所有包。

(6)根据ServletContainerInitializer查询结果以及javax.servlet.annotation.HandlesTypes注解配置,初始化typeinitializerMap和initializerClassMap两个映射(主要用于后续的注解检测),前者表示类对应的ServletContainerInitializer集合,而后者表示每个ServletContainerInitializer对应的类的集合,具体类由javax.servlet.annotation.HandlesTypes注解指定。

(7)当“主WebXml”的metadataComplete为false或者typeInitializerMap不为空时:

     处理WEB-INF/class下的注解,对于该目录下的每个类应该做如下处理:

  1. 检测javax.servlet.annotation.HandlesTypes注解。
  2. 当WebXml的metadataComplete为false,查找javax.servlet.annotation.WebServlet、javax.servlet.annotation.WebFilter、javax.servlet.annotation.WebListener注解配置,将其合并到“主WebXml”。

     处理JAR包内的注解妹纸处理包含web-fragment.xml的JAR,对于JAR包中的每一个类做如下处理:

  1. 检测javax.servlet.annotation.HandlesTypes注解;
  2. 当“主WebXml”和“片段WebXml”的metadataComplete均为false,查找javax.servlet.annotation.WebServlet、javax.servlet.annotation.WebFilter、javax.servlet.annotation.WebListener注解配置,将其合并到“片段WebXml”。

(8)如果“主WebXml”的metadataComplete为false,将所有的“片段WebXml”按照排序顺序合并到“主WebXml”。

(9)将“默认WebXml”合并到“主WebXml”。

(10)配置JspServlet。对于当前Web应用中JspFile属性不为空的Servlet,将其servletClass设置为org.apache.jasper.servlet.JspServlet(Tomcat提供的引擎),将JspFile设置为Servlet的初始化参数,同时将名称为“Jsp”的Servlet(见conf/web.xml)的初始化参数也复制到该Servlet中。

(11)使用“主WebXml”配置当前StandardContext,包括Servlet、Filter、Listener等Servlet规范中支持的组件。对于ServletContext层级的对象,直接由StandardContext维护,对于Servlet,则创建StandardWrapper子对象,并且添加到StandardContext实例。

(12)将合并后的WebXml保存到ServletContext属性中,便于后续处理复用,属性,名称为org.apache.tomcat.util.scan.MergedWebXml。

(13)查找JAR包“META-INF/resources/”下的静态资源,并添加到StandardContext。

(14)将ServletContainerInitializer扫描结果添加到StandardContext,以便StandardContext启动时使用

    至此,StandardContext在正式启动StandardWrapper子对象之前,完成了Web应用容器的初始化,包括Servlet规范中设计的各类组件、注解以及可编程方式的支持。

【2】应用程序注解配置
    当StandardContext的ignoreAnnotations为false时,Tomcat支持读取如下接口的Java命名服务注解配置,添加相关的JNDI资源引用,以便在实例化相关接口时,进行JNDI资源依赖注入。

六、StandardWrapper

    StandardWrapper具体维护了Servlet实例,而在StandardContext启动过程中,StandardWrapper的处理分为两部分:

  • 首先,当通过ContextConfig完成Web日期初始化后,先调用StandardWrapper.start,此时StandardWrapper组件的状态将变成STARTED(出广播启动通知外,不进行其他处理)。
  • 其次,对启动时加载的Servler(load-on-startup>=0),调用StandardWrapper.load,完成Servlet的加载。

    StandardWrapper的load过程具体如下:

  1. 创建Servlet实例,如果添加了JNDI资源注解,将进行依赖注入。
  2. 读取javax.servlet.annotation.MultipartConfig注解配置,以用于multipart/form-data请求处理,包括临时文件存储路径、上次文件最大字节数、请求最大字节数、文件大小阀值。
  3. 读取javax.servlet.annptation.ServletSecurity()注解配置,添加Servlet安全。
  4. 调用javax.servlet.Servlet.init()方法进行Servlet初始化。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值