0%

tomcat之web应用加载

Web应用加载

Catalina对于Web应用的加载主要由StandardHost、HostConfig、StandardContext、ContextConfig、StandardWrapper这五个类完成。

StandardHost

StandardHost加载Web应用(即StandardContext)的入口有两个。

一个入口是Catalina构造Server实例时,如果Host元素存在Context子元素时,那么Context元素将会作为Host容器的子容器加到Host实例中,并在Host启动时,由生命周期管理接口的start()方法启动

此时Context的配置为

1
2
3
4
<Host name="localhost" appBase="webapps" unparkWARs="true" autoDeploy="true">
<!-- docBase是Web应用根目录的文件路径 path为Web应用根请求地址 -->
<Context docBase="myApp" path="myApp" reloadable="true"/>
</Host>

这种配置需要每次部署新的Web应用或者删除旧应用时,都必须修改一下server.xml文件。

StandardHost启动加载过程

  • 为Host添加一个Value实现ErrorReportValue,该类主要用于在服务器处理异常时输出错误页面。如果没有在web.xml中配置错误处理页面,Tomcat返回的异常栈页面便是由ErrorReportValue生成的。

  • 调用StandardHost父类ContainerBase的startInternal()方法启动虚拟主机,处理步骤为

    • 如果配置了集群组件Cluster,则启动

    • 如果配置了安全组件Realm,则启动

    • 启动子节点(server.xml中创建的StandardContext实例)

    • 启动Host持有的Pipeline组件

    • 设置HOST状态为STARTING,触发START_EVENT生命周期时间。HostConfig监听该事件,扫描Web部署目录,对于部署描述文件、WAR包、目录会自动创建StandardContext实例,添加到Host并启动

    • 启动Host层级的后台任务处理:cluster后台任务处理、Realm后台任务处理、Pipeline中Value的后台任务处理

HostConfig

另一个入口则是由HostConfig自动扫描部署目录

1
2
3
4
<!-- 默认的server.xml配置 -->
<!-- appBase为web应用部署的基础目录,所有需要部署的Web应用都会复制到此目录下,Tomcat通过HostConfig完成该目录下Web应用的自动部署 -->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">

生命周期事件

HostConfig处理的生命周期事件包括:START_EVENT、PERIODIC_EVENT、STOP_EVENT

START_EVENT事件

在Host启动时触发,完成服务器启动过程中的Web应用部署(只有当Host的deployOnStartup属性为true时,服务器才会在启动过程中部署web应用,默认为true)

该事件完成了Context描述文件部署、web目录部署、WAR包部署,对应于3种不同的部署方式

Context描述文件部署

Tomcat支持通过一个独立的Context描述文件来配置Web应用,该配置文件的存储路径由Host的xmlBase属性指定。默认为$CATALINA_BASE/conf/<Egnine名称>/<Host名称>,对于Tomcat默认的Host,$CATALINA_BASE/conf/Catalina/localhost

在该目录下创建文件,为myApp.xml

1
2
3
<Context docBase="test/myApp" path="/myApp" reloadable="false">
<WatchdResource>WEB-INF/web.xml</WatchdResource>
</Context>

将myApp的web应用复制到test目录下,Tomcat启动时便会自动部署该Web应用

web目录部署

以目录的形式发布并部署到Web应用是Tomcat中最常见的部署方式,只需将包含Web应用所有资源文件、jar包、描述文件的目录复制到Host指定的appBase目录下即可完成部署

根据web应用中的配置文件来实例化Context(默认为META-INF目录下的context.xml),但是无法覆盖name、path、webappVersion、docBase这四个属性,这些由Web目录的路径及名称确定

WAR包部署

与web目录部署类似

PERIODIC_EVENT事件

用于定时扫描Web应用的变更,并进行重新加载

StandardContext

包含了具体的Web应用初始化及启动工作,该部分工作由组件Context完成

StandardContext的启动过程

  • 发布正在启动的JMX通知,可以通过NotifycationListener来监听Web应用启动

  • 启动当前Context维护的JNDI资源

  • 初始化当前Context使用的WebResourceRoot并启动

    • pre资源:在context.xml中通过配置的资源

    • Main资源:web应用目录、WAR包或者WAE包解压目录包含的文件,这些资源的查找顺序为WEB-INF/classes、WEB-INF/lib

    • Jar资源:配置的资源

    • Post资源:配置的资源

  • 创建web应用类加载器WebappLoader

  • 如果没有设置Cookie处理器,则创建默认的Rfc6265CokkieProcessor

  • 设置字符集映射CharsetMapper,该映射主要用于根据Locale获取字符集编码

  • 初始化临时目录,默认为$CATALINA_BASE/work/<Egnine名称>/<Host名称>/<Context名称>

  • web应用的依赖检测,主要检测依赖扩展点完整性

  • 如果当前Context使用JNDI,则添加NamingContextListener

  • 启动web应用类加载器WebappLoader.start

  • 启动安全组件Realm

  • 发布CONFIGURE_START_EVENT事件,ContextConfig监听该事件以完成Servlet的创建

  • 启动Context子节点Wrapper

  • 启动Context维护的Pipeline

  • 创建会话管理器

  • 将Context的web资源集合添加到ServletContext属性,属性名为org.apache.catalina.resources

  • 创建实例管理器InstanceManager,用于创建对象实例,如Servlet、Filter等

  • 将Jar包扫描器JarScanner添加到ServletContext属性

  • 合并ServletContext初始化参数和Context组件中的ApplicationParameter。

  • 启动添加到当前Context的ServletContainerInitializer

  • 实例化应用监听器ApplicationListener,分为事件监听器(ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener、HttpSessionIdListener、HttpSessionAttributeListener)以及生命周期监听器(HttpSessionListener、ServletContextListener)

  • 检测未覆盖的HTTP方法的安全约束

  • 启动会话管理器

  • 实例化FilterConfig、Filter,并调用Filter.init初始化

  • 对于loadOnStartUp>=0的Wrapper,调用Wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init进行初始化

  • 启动后台定时处理线程

  • 发布正在运行的JMX通知

  • 调用WebResourceRoot.gc()释放资源

  • 设置Context的状态,如果启动成功,设置为STARTING,否则设为FAILED

ContextConfig

Context创建时会默认添加一个生命周期监听器–ContextConfig,一共处理6类事件,与Context启动有关系的3类:AFTER_INIT_EVENT、BEFORE_START_EVENT、CONFIGURE_STRAT_EVENT

AFTER_INIT_EVENT事件

属于Context初始化阶段,主要用于Context属性的配置工作

Context的创建分为三种方式

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

该事件负责将tomcat提供的默认配置也一并添加到Context实例

  • 如果Context的override属性为false(表示使用的默认配置)

    • 如果存在conf/context.xml文件,那么解析该文件,更新当前Context实例属性

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

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

Tomcat中Context属性优先级是 configFile ->conf/<Engine名称>/<Host名称>/context.xml.default ->conf/context.xml

BEFORE_START_EVENT事件

该事件在Context启动之前触发用于更新Context的docBase属性和解决web目录锁的问题

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

处理过程(ContextConfig.fixDocBase)

  • 根据Host的appBase以及Context的docBase计算docBase的绝对路径
  • 如果docBase为一个WAR文件,则需要解压部署
  • 如果docBase为一个有效目录,而且存在与该目录同名的WAR包,则解压部署,覆盖之前的文件
  • 如果docBase为一个不存在的目录,但是存在与该目录同名的WAR包,则解压部署

CONFIGURE_START_EVENT事件

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

该事件工作内容

  • 根据配置创建Wrapper(Servlet)、Filter、ServletContextListener等,完成web容器的初始化

  • 如果StandardContext的ignoreAnnotations为false,则解析应用程序注解配置,添加相关JNDI资源引用

  • 基于解析完成的web容器,检测web应用部署描述文件中使用的安全角色名称,当发现使用了未定义的角色时,提示警告同时将未定义的角色添加到Context安全角色列表中

  • 当Context需要进行安全认证但是没有指定具体的Authenticator时,根据服务器配置自动创建默认实例

web容器初始化
  • 解析默认配置,生成WebXml对象

  • 解析Web应用的web.xml文件

  • 扫描web应用所有的JAR包,如果包含META-INF/web-fragment.xml,则解析文件并创建WebXml对象(部分片段)

  • 将web-fragment.xml创建的WebXml对象按照Servlet规范进行排序,同时将排序结果对应的JAR文件名列表设置到ServletContext属性中,javax.servlet.context.orderedLibs

  • 查找ServletContainerInitializer实现,并创建实例,查找范围分为两部分

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

    • 容器包:搜索所有的包

  • 根据ServletContainerInitializer查询结果以及javax.servlet.annotation.HandlersTypes注解配置,初始化typeInitializerMap和initializerClassMap两个映射

  • 当主Web.xml的metadataComplete为false或者typeInitializerMap不为空时,处理注解

  • 当主Web.xml的metadataComplete为false,将所有的片段WebXml按照顺序排序合并至主WebXml

  • 将默认WebXml合并至主WebXml

  • 配置JspServlet

  • 使用主WebXml配置当前StandardContext

  • 将合并后的WebXml保存到ServletContext属性中

  • 查找JAR包META-INF/resources/下的静态资源,并添加到StandardContext

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

应用程序注解配置

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

支持读取的接口

  • web应用程序监听器

    • javax.servlet.ServletContextAttributeListener

    • javax.servlet.ServletRequestListener

    • javax.servlet.ServletRequestAttributeListener

    • javax.servlet.http.HttpSessionAttributeListener

    • javax.servlet.http.HttpSessionListener

    • javax.servlet.ServletContextListener

  • javax.servlet.Filter

  • javax.servlet.Servlet

支持读取的注解包括类注解、属性注解、方法注解

  • 类:javax.annotation.Resource、javax.annotation.Resources

  • 属性和方法:javax.annotation.Resource

StandardWrapper

StandardWrapper具体维护了Servlet实例,StandardWrapper的处理分为两部分

  • 当通过ContextConfig完成web容器初始化后,先调用StandardWrapper.start,此时StarndardWrapper组件状态将变为STARTED
  • 对于启动时加载的Servlet,调用StandardWrapper.load,完成Servlet的加载
    • 创建Servlet实例,如果添加了JNDI资源注解,将进行资源注入
    • 读取javax.servlet.annotation.MultipartConfig注解配置,以用于multipart/form-data请求处理,包括临时文件存储路径、上传文件最大字节数、请求最大字节数、文件大小阈值
    • 读取javax.servlet.annotation.ServletSecurity()注解配置,添加Servlet安全
    • 调用javax.servlet.Servlet.init()方法进行Servlet初始化

Context的命名规则

Context的name、path、version与文件名称相关

当未指定version时,name与path相同,如果path为空字符,基础文件名称为ROOT;否则,将path起始的/删除,其余/替换为#的基础文件

path为/foo/bar name为/foo/path 基础文件名称为foo#bar

如果指定了version,则path不变,name和基础文件名称将追加##和具体版本号

path为/foo/bar version为2 name为/foo/bar##2 基础文件名称为foo#bar##2