最近在项目中遇到了启动时出现加载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 --> servlet
注意:
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() 方法的。
(可以通过spring中@Order注解来进行初始化顺序的配置 )
@Order注解规则:
(1)order的值越小,优先级越高
(2)order如果不标注数字,默认最低优先级,因为其默认值是int的最大值
(3)该注解等同于实现Order接口的getOrder方法并返回数字
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
/**
* The order value.
* <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;
}
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
接着让我们回忆一下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-starup元素在web应用启动的时候指定了servlet被加载的顺序,它的值必须是一个整数。
如果它的值是一个负数或者是这个元素不存在,那么容器会在servlet被调用的时候加载这个servlet。
如果值是正整数或零,容器再配置的时候就加载并初始化这个servlet,容器必须保证值小的先被加载。如果值相等,容器就可以自动选择先加载谁。
正整数的值越小,启动该servlet的优先级越高。
加载spring:
比如filter过滤器用到bean,但是加载顺序是:先加载filter后加载spring,则filter中spring初始化操作中的bean为null;
所以,如果过滤器中要使用到 bean,可以将spring 的加载 改成 Listener的方式 :
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
或者从配置文件中获取
最终结论:
web.xml的对应加载顺序是:context-->param-->listener-->filter-->servlet-->spring,而同类型的节点之间的实际程序调用的时候的顺序是根据对应的mapping的顺序进行调用的。