http://www.360doc.com/content/10/1223/08/1720440_80574231.shtmlhtml
通常的Web项目都会在web.xml中加入Spring监听器,内容以下:java
1
2
3
4
5
6
7
8
|
<
listener
>
<
listener-class
>org.springframework.web.context.ContextLoaderListener</
listener-class
>
</
listener
>
<
context-param
>
<
param-name
>contextConfigLocation</
param-name
>
<
param-value
>classpath*:applicationContext-struts.xml,classpath*:spring/applicationContext.xml</
param-value
>
</
context-param
>
|
咱们的问题是,Spring是什么时候以及如何加载咱们的配置文件来初始化Bean工厂的,带着这些问题,咱们展开研究:web
咱们先来看看web.xml中配置的监听器的类,来回答咱们的问题,Spring是什么时候来加载咱们的配置文件的:spring
org.springframework.web.context.ContextLoaderListener服务器
它继承了javax.servlet.ServletContextListener接口。app
ServletContextListener是J2EE Servlet API中的一个标准接口,函数
它可以监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。post
当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。this
这里面有两个方法咱们比较感兴趣:spa
1
2
3
4
5
6
7
|
/**
* Create the ContextLoader to use. Can be overridden in subclasses.
* @return the new ContextLoader
*/
protected
ContextLoader createContextLoader() {
return
new
ContextLoader();
}
|
这个方法构造一个默认的ContextLoader,ContextLoader能够理解为Spring上下文的加载器。之因此这样去定义这样一个类,是为了开发人员进行重写此方法来使用一个自定义的Spring上下文的加载器。
1
2
3
4
5
6
7
|
/**
* Initialize the root web application context.
*/
public
void
contextInitialized(ServletContextEvent event) {
this
.contextLoader = createContextLoader();
this
.contextLoader.initWebApplicationContext(event.getServletContext());
}
|
这个方法很简单,仅仅只是调用了createContextLoader()构造了ContextLoader,并调用其初始化方法。
由此,咱们能够得出结论,Spring是在Web项目启动时,经过ServletContextListener机制,来加载以及初始化Spring上下文的。
下面,咱们好好研究一下Spring是如何加载其上下文的:
咱们先定位ContextLoader类。
看看此类的initWebApplicationContext()方法(省略了不重要的语句)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/**
* Initialize Spring's web application context for the given servlet context,
* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @throws IllegalStateException if there is already a root application context present
* @throws BeansException if the context failed to initialize
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public
WebApplicationContext initWebApplicationContext(ServletContext servletContext)
throws
IllegalStateException, BeansException {
if
(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) !=
null
) {
throw
new
IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+
"check whether you have multiple ContextLoader* definitions in your web.xml!"
);
}
try
{
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
this
.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this
.context);
currentContextPerThread.put(Thread.currentThread().getContextClassLoader(),
this
.context);
return
this
.context;
}
catch
(RuntimeException ex) {
logger.error(
"Context initialization failed"
, ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw
ex;
}
catch
(Error err) {
logger.error(
"Context initialization failed"
, err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw
err;
}
}
|
其中的有两句比较重要,咱们来看看:
ApplicationContext parent = loadParentContext(servletContext);
这个方法的用途主要是用来解决Spring共享环境的,即,若是咱们有多个WAR包部署在同一个服务器上,并且这些WAR都共享某一套业务逻辑层。如何共享一套业务逻辑包配置而不要每一个WAR都单独配置,这时咱们就可能须要Spring的共享环境了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
protected
ApplicationContext loadParentContext(ServletContext servletContext)
throws
BeansException {
ApplicationContext parentContext =
null
;
// 从web.xml中读取父工厂的配置文件,默认为:"classpath*:beanRefContext.xml"
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
// 从web.xml中读取父类工厂的名称
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if
(parentContextKey !=
null
) {
// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
this
.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext)
this
.parentContextRef.getFactory();
}
return
parentContext;
}
|
如今咱们引入BeanFactoryLocator,它是Spring配置文件的一个定位器,Spring官方给它的定义是用来查找,使用和释放一个BeanFactory或其子类的接口。下面咱们看看此图:
ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
是根据参数locatorFactorySelector去一个单例工厂中去拿一个对应的BeanFactoryLocator,也即,若是工厂中没有对应于locatorFactorySelector的BeanFactoryLocator对象,那就返回一个新的BeanFactoryLocator实例(这里是ContextSingletonBeanFactoryLocator的实例),不然,就从工厂里取现有的BeanFactoryLocator对象。
ContextSingletonBeanFactoryLocator里维护了一个静态的Map对象instances,每次须要新增BeanFactoryLocator实例时都会更新这个Map对象,这个Map对象是以配置文件名为KEY,BeanFactoryLocator对象为值。缘由很简单,就是但愿同一个配置文件只被初始化一次。
若是没有在web.xml中定义locatorFactorySelector这个参数,父环境的配置文件默认使用:"classpath*:beanRefContext.xml"
this.parentContextRef = locator.useBeanFactory(parentContextKey);
此方法定义在SingletonBeanFactoryLocator类中,一样是一个单例工厂模式,判断传入的参数parentContextKey对应的BeanFactory是否有被初始化,通过上面的ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector)指定Spring父环境配置文件,这个方法判断指定的父环境是否被初始化,若是有则返回,没有就进行初始化。看看此方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public
BeanFactoryReference useBeanFactory(String factoryKey)
throws
BeansException {
synchronized
(
this
.bfgInstancesByKey) {
BeanFactoryGroup bfg = (BeanFactoryGroup)
this
.bfgInstancesByKey.get(
this
.resourceLocation);
if
(bfg !=
null
) {
bfg.refCount++;
}
else
{
// Create the BeanFactory but don't initialize it.
BeanFactory groupContext = createDefinition(
this
.resourceLocation, factoryKey);
// Record its existence now, before instantiating any singletons.
bfg =
new
BeanFactoryGroup();
bfg.definition = groupContext;
bfg.refCount =
1
;
this
.bfgInstancesByKey.put(
this
.resourceLocation, bfg);
this
.bfgInstancesByObj.put(groupContext, bfg);
// Now initialize the BeanFactory. This may cause a re-entrant invocation
// of this method, but since we've already added the BeanFactory to our
// mappings, the next time it will be found and simply have its
// reference count incremented.
try
{
initializeDefinition(groupContext);
}
catch
(BeansException ex) {
this
.bfgInstancesByKey.remove(
this
.resourceLocation);
this
.bfgInstancesByObj.remove(groupContext);
throw
new
BootstrapException(
"Unable to initialize group definition. "
+
"Group resource name ["
+
this
.resourceLocation +
"], factory key ["
+ factoryKey +
"]"
, ex);
}
}
try
{
BeanFactory beanFactory =
null
;
if
(factoryKey !=
null
) {
beanFactory = (BeanFactory) bfg.definition.getBean(factoryKey, BeanFactory.
class
);
}
else
if
(bfg.definition
instanceof
ListableBeanFactory) {
beanFactory = (BeanFactory) BeanFactoryUtils.beanOfType((ListableBeanFactory) bfg.definition, BeanFactory.
class
);
}
else
{
throw
new
IllegalStateException(
"Factory key is null, and underlying factory is not a ListableBeanFactory: "
+ bfg.definition);
}
return
new
CountingBeanFactoryReference(beanFactory, bfg.definition);
}
catch
(BeansException ex) {
throw
new
BootstrapException(
"Unable to return specified BeanFactory instance: factory key ["
+
factoryKey +
"], from group with resource name ["
+
this
.resourceLocation +
"]"
, ex);
}
}
}
|
此方法分为两做了两件事,
第一,初始化上下文,主意这里初始化的是从web.xml配置参数里的Spring配置文件,也是上面讲loadParentContext方法里的
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
这句指定的参数。这里初始化的是这个配置文件全部Bean。咱们指定的factoryKey对应的Bean也是其中之一。
第二,从已经初始化的Spring上下文环境中获取Spring父环境。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<
beans
>
<
bean
id
=
"factoryBeanId"
class
=
"org.springframework.context.support.ClassPathXmlApplicationContext"
>
<
constructor-arg
>
<
list
>
<
value
>sharebean.xml</
value
>
</
list
>
</
constructor-arg
>
</
bean
>
<
bean
id
=
"factoryBeanId2"
class
=
"org.springframework.context.support.ClassPathXmlApplicationContext"
>
<
constructor-arg
>
<
list
>
<
value
>sharebean2.xml</
value
>
</
list
>
</
constructor-arg
>
</
bean
>
</
beans
>
|
1
2
3
4
5
6
7
8
9
10
|
<!—========================= web.xml ========================= -->
<
context-param
>
<
param-name
>locatorFactorySelector</
param-name
>
<
param-value
>beanRefFactory.xml</
param-value
>
</
context-param
>
<
context-param
>
<
param-name
>parentContextKey</
param-name
>
<
param-value
>factoryBeanId</
|