Spring 初始化的一些研究


最近遇到SpringMVC里bean加载两次的问题

对Spring加载机制表示有点兴趣,特此研究了Spring初始化的一些细节。

注意:以下的文档源码基于Spring 4.2.3.RELEASE版本。

ServletContext & ApplicationContext

首先明确一下ServletContext 和 ApplicationContext 的概念。

ServletContext

每一个Web应用在启动时,Servlet容器会创建一个servletContext对象。每个web应用有唯一的servletContext对象,同一个web应用的所有servlet对象共享一个serveltContext,servlet对象可以通过它来访问容器中的各种资源。可以说,ServletContext是servlet与servlet容器之间的直接通信的接口。

servletContext接口提供的方法,一部分用于在Web应用范围内存取共享数据,类似Map一样基于key-value键值对形式。

web应用范围具有以下两层含义:
1. 表示有web应用的生命周期构成的时间段.
2. 表示在web应用的生命周期内所有web组件的集合。
(摘自:点我

ApplicationContext

BeanFactory的子类。BeanFactory可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。

ApplicationContext在基础上,增加了很多web需要的功能。从属性文件从解析文本信息和将事件传递给所指定的监听器。这个容器在 org.springframework.context.ApplicationContext interface 接口中定义。

在源码中我们会看到ApplicationContext是在ServletContext中以key-value值存在的。

ContextLoaderListener

作用:寻找初始化bean的配置文件,初始化WebApplicationContext并将其放置在ServletContext中。

与DispatcherServlet生成的WebApplicationContext不同,ContextLoaderListener生成的WebApplicationContext在Spring中作为Root WebApplicationContext使用。

至于DispatcherServlet生成的WebApplicationContext在讨论完ContextLoaderListener后讨论。

WebApplicationContext的初始化

首先进入ContextLoaderListener,除了构造方法以外只有两个方法,都是ContextLoaderListener实现ServletContextListener的方法。

 

contextInitialized()带有ServletContextEvent参数,getServletContext()可以获取ServletContext对象。

进入init方法,我们会发现init主要对context进行生成、判断context类型、放置进servletContext,并且将其设置为当前线程(就是主线程)的context。
init方法是ContextLoader中的,它创建的是 XmlWebApplicationContext这样一个类,它实现的接口是

WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext->BeanFactory

这样一来spring中的所有bean都由这个类来创建。

以下是init的具体方法。

 

首先检查servletContext是否已经存储了一个默认名称的WebApplicationContext,因为在一个应用中Spring容器只能有一个,所以需要校验,如果有一个的话则直接抛出异常。

接下来就是context的create过程,调用了createWebApplicationContext方法,传入了获取的servletContext对象。

 

上面两个方法我们可以得到一些信息:

第一是Spring通过反射机制创建application对象。

第二是可变策略,可配置contextName参数,如果没有也不会炸,他有一个默认的。

首先在determineContextClass方法,通过判断ServletContext对象有没有init参数contextClass从而确定其加载策略。init参数的配置通过web.xml的<context-param>标签配置。

如果有配置这个参数,则会读取这个参数的值作为context的类名使用默认的类加载器加载applicationContext。如果类名妹有填对那当然就炸了。

如果妹有配置这个参数就会使用默认配置,获取WebApplicationContext的className,并且使用ContextLoader的ClassLoader来加载一个WebApplicationContext的默认实现类XmlWebApplicationContext对象返回,作为整个Spring的applicationContext。

回到createWebApplicationContext,进入instantiateClass方法可以看到它获取了contextClass的DeclaredConstructor(空构造参数)来创建application对象。贼基八典型的反射类加载机制。这里就不赘述了,不知道的自己去看反射的ClassLoader机制。

而对于XmlWebApplicationContext这个类在org.springframework.web.context.support包下。
它提供了默认的策略,代码如下:

 

也就是说在ContextLoader.java的路径下,有一个ContextLoader.properties文件,这里配置了Spring默认的WebApplicationContext接口实现类。

获取到这个application对象之后,就是对application的一些判断并进行操作,代码如下:

 

默认创建的WebApplicationContext是继承了ConfigurableWebApplicationContext的,所以这段代码一定会被执行。isActive()方法判断context是否已经被刷新,refresh的代码会在稍后解析。所谓刷新(refresh)的意思大概就是执行beanFactory的实例化bean的方法,完成你在spring.xml里面配置的那一堆Bean的解析、实例化及注册。至于parent,为可配置,没有就是返回null。

现在来看一下这段代码做的最重要的一件事,就是执行configureAndRefreshWebApplicationContext进而实例化bean。

 

这段代码我们看到了CONFIG_LOCATION_PARAM这个参数,这个参数的值是"contextConfigLocation"。是不是贼熟悉?就是我们一般在web.xml配置的那个参数,用于自定义application.xml的命名和位置。

如果你不定义,XmlWebApplicationContext有他的默认策略。

这也就是为什么默认以applicationContext.xml为配置文件名,/WEB-INF/为默认目录。

而获取到这个位置后,获取了wac的environment,就可以refresh了。

refresh方法在完成BeanDefinition前,用于完成Bean的解析、实例化及注册。

回到initWebApplicationContext方法,初始化后的context存到了servletContext中,具体的就是存到了一个Map变量中,key值就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE这个常量。

加载步骤完成以后,就可以通过

来获取WebApplicationContext。

总结一下,这个init方法的过程,大概就像注释里写的那样:

 

 

参考资料 :上下文ContextLoaderListener的应用

声明:夜语|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Spring 初始化的一些研究


我不和你谈事,只想和你谈心。🍂