一、背景
最近在项目中遇到了启动时出现加载service注解注入失败的问题,后来经过不懈努力发现了是因为web.xml配置文件中的元素加载顺序导致的,那么就抽空研究了以下tomcat在启动时web.xml文件中元素的加载顺序,现在和大家分享。
二、问题剖析和研究结果
遇到这种问题的时候,一般看源码是最直接和最权威的获取答案的方式,根据tomcat架构设计Context的实现类是StandardContext,全称org.apache.catalina.core.StandardContext。看到其实现Lifecycle接口,我们在StandardContext中找到startInternal方法,下面给出我把暂时无用的代码去掉后的注释版源码:
1 /** 2 * Start this component and implement the requirements 3 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. 4 * 5 * @exception LifecycleException if this component detects a fatal error 6 * that prevents this component from being used 7 */ 8 @Override 9 protectedsynchronized void startInternal() throwsLifecycleException { 10 //设置webappLoader 代码省略 11 12 // Standard container startup 代码省略 13 14 try{ 15 16 // Set up the context init params 17 //初始化context-param节点数据 18 mergeParameters(); 19 20 21 // Configure and call application event listeners 22 //配置和调用应用程序事件listeners 23 if(ok) { 24 if(!listenerStart()) { 25 log.error("Error listenerStart"); 26 ok = false; 27 } 28 } 29 30 // Configure and call application filters 31 //配置和调用应用程序filters 32 if(ok) { 33 if(!filterStart()) { 34 log.error("Error filterStart"); 35 ok = false; 36 } 37 } 38 39 // Load and initialize all "load on startup" servlets 40 //加载和初始化配置在load on startup的servlets 41 if(ok) { 42 loadOnStartup(findChildren()); 43 } 44 45 // Start ContainerBackgroundProcessor thread 46 super.threadStart(); 47 }finally{ 48 // Unbinding thread 49 unbindThread(oldCCL); 50 } 51 52 }
那我们接着归纳和整理一下代码:
1.首先初始化context-param节点
2.接着配置和调用listeners 并开始监听
3.然后配置和调用filters filters开始起作用
4.最后加载和初始化配置在load on startup的servlets
即元素加载顺序为:
context-param --> listeners --> filters --> servlets
注意:
1.该加载顺序并不会受元素在web.xml文件中的位置的影响。
2.但对于某类配置节而言,与它们出现的顺序是有关的。以 filter 为例,web.xml 中当然可以定义多个 filter,与 filter 相关的一个配置节是 filter-mapping,这里一定要注意,对于拥有相同 filter-name 的 filter 和 filter-mapping 配置节而言,filter-mapping 必须出现在 filter 之后,否则当解析到 filter-mapping 时,它所对应的 filter-name 还未定义。web 容器启动时初始化每个 filter 时,是按照 filter 配置节出现的顺序来初始化的,当请求资源匹配多个 filter-mapping 时,filter 拦截资源是按照 filter-mapping 配置节出现的顺序来依次调用 doFilter() 方法的。
接着让我们来回忆一下web项目的启动顺序
1.web容器读取web.xml配置文件,并首先读取<context-param>和<listener>两个结点。
2.容器创建一个ServletContext(servlet上下文),该web项目的所有部分都将共享这个上下文。
3.容器将<context-param>转换为键值对,并交给servletContext。
4.容器按照load on startup中的启动顺序创建<listener>中的类实例,创建监听器。
关于load on startup
load-on-startup 元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。
如果它的值是一个负整数或是这个元素不存在,那么容器会在该servlet被调用的时候,加载这个servlet 。
如果值是正整数或零,容器在配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器可以自动选择先加载谁。
正数的值越小,启动该servlet的优先级越高。
三、总结
通过研究源码我们明白了web.xml中各个元素的加载顺序,再遇到这种问题,我们就可以很快的定位出问题所在了。由此也发现和体会到了研究源码是一种很好的习惯也是解决问题不可缺少的方式。