Web应用加载
Catalina对于Web应用的加载主要由StandardHost、HostConfig、StandardContext、ContextConfig、StandardWrapper这五个类完成。
StandardHost
StandardHost加载Web应用(即StandardContext)的入口有两个。
一个入口是Catalina构造Server实例时,如果Host元素存在Context子元素时,那么Context元素将会作为Host容器的子容器加到Host实例中,并在Host启动时,由生命周期管理接口的start()方法启动
此时Context的配置为
1 | <Host name="localhost" appBase="webapps" unparkWARs="true" autoDeploy="true"> |
这种配置需要每次部署新的Web应用或者删除旧应用时,都必须修改一下server.xml文件。
StandardHost启动加载过程
为Host添加一个Value实现ErrorReportValue,该类主要用于在服务器处理异常时输出错误页面。如果没有在web.xml中配置错误处理页面,Tomcat返回的异常栈页面便是由ErrorReportValue生成的。
调用StandardHost父类ContainerBase的startInternal()方法启动虚拟主机,处理步骤为
如果配置了集群组件Cluster,则启动
如果配置了安全组件Realm,则启动
启动子节点(server.xml中
<Context>
创建的StandardContext实例)启动Host持有的Pipeline组件
设置HOST状态为STARTING,触发START_EVENT生命周期时间。HostConfig监听该事件,扫描Web部署目录,对于部署描述文件、WAR包、目录会自动创建StandardContext实例,添加到Host并启动
启动Host层级的后台任务处理:cluster后台任务处理、Realm后台任务处理、Pipeline中Value的后台任务处理
HostConfig
另一个入口则是由HostConfig自动扫描部署目录
1 | <!-- 默认的server.xml配置 --> |
生命周期事件
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 | <Context docBase="test/myApp" path="/myApp" reloadable="false"> |
将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中通过
<PreResources>
配置的资源Main资源:web应用目录、WAR包或者WAE包解压目录包含的文件,这些资源的查找顺序为WEB-INF/classes、WEB-INF/lib
Jar资源:
<JarResources>
配置的资源Post资源:
<PostResources>
配置的资源
创建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