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</
|