基於版本4.1.7.RELEASE
ContextLoader :應用root application context初始化的實際執行着,被ContextLoaderListener調用
構造函數:
public ContextLoader() {
}
根據servlet配置中的contextClass和contextConfigLocation來創建web application context,在其子類ContextLoaderListener被申明的時候會調用默認的構造函數。
帶參數的構造函數:
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
context作用同ContextLoaderListener中說明一樣,只是ContextLoaderListener調用super(context)的時候會調用到此方法中,然後將context賦值給類屬性,查看類屬性的申明:
/**
* The root WebApplicationContext instance that this loader manages.
*/
private WebApplicationContext context;
即可明白通過構造函數設置的是root WebApplicationContext
下面來看在ContextLoaderListener的初始化事件通知中所調用的initWebApplicationContext方法
/**
* 通過參數servletContext初始化WebApplicationContext,使用構造時提供的WebApplicationContext或者根據contextClass和contextConfigL * ocation(在web.xml定義)創建一個新的WebApplicationContext
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//防止重複初始化
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException;
}
try {
//如果當前的rootWebApplicationContext爲空 則創建一個,如果不爲空有可能是構造時傳進來的
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//判斷是否active的條件是,至少被refresh了1次並且沒有被關閉。refresh可以理解爲同步配置數據
if (!cwac.isActive()) {
// context沒有被refresh
if (cwac.getParent() == null) {
// context沒有明確的父容器 讀取父容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置和刷新context
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//將context保存到WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//這裏根據classloader來判斷當前線程是否是加載ContextLoader的線程。
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
return this.context;
}
catch (RuntimeException ex) {
}
catch (Error err) {
}
}
這裏有幾個疑問,parent是什麼,還有線程和context的對應關係是用來做什麼的?
對於parent可以看loadParentContext這個方法:
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
//獲取web.xml配置文件中指定的locatorFactorySelector,如果沒有配置這個選項,默認指向“class path*:beanRefContext.xml”
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
//獲取web.xml配置文件中指定的parentContentKey,這個key指向一個被加載完成的BeanFactory
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (parentContextKey != null) {
//獲取beanFactory定位器
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
//根據定位器查找到指定的BeanFactory,該beanFactory加載了beanRefContext.xml(默認情況下),在設置配置文件路徑完成後調用了自身的refresh,所以已經將配置文件中定義的內容加載完成了,而完成加載的ApplicationContext就是這裏要返回的parentContext
this.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
這個方法一般是在EJB或者EAR需要共享容器的時候使用,對於一般的WEB型應用,由於不配置locatorFactorySelector,所以這個方法實際上並沒有運行。
線程和context的對應關係存放在currentContextPerThread中,該變量的類型是Map<ClassLoader,WebApplicationContext>,至於爲什麼這麼存,
在closeWebApplicationContext中我們可以一窺端倪
public void closeWebApplicationContext(ServletContext servletContext) {
try {
//關閉當前的Context,關閉過程中會產生關閉事件通知,銷燬當前Context關聯的bean和beanFactory,回調子類的onClose方法,active設置成false。
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
//釋放ClassLoader和Context對應列表中當前ClassLoader對應的Context
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (this.parentContextRef != null) {
//釋放對parent的引用
this.parentContextRef.release();
}
}
}
currentContextPerThread是一個靜態類型的引用,目的就是保存不同的ClassLoader對應的Context以防止在銷燬Context時出現錯誤關閉的情況。
在上面的處理過程中,configureAndRefreshWebApplicationContext做了以下幾件事
1 設置ID,把ServletContext中的ID賦值給WebApplicationContext,如果沒有,則設置默認值
2 根據contextConfigLocation參數(web.xml)指定的地址,找到spring application的xml文件
3 初始化環境變量
4 根據定製的ApplicationContextInitializer初始化Context,這些定製器必須實現ApplicationContextInitializer 並且在contextInitializerClasses(web.xml)申明出來
5 刷新調用refresh方法
由此我們可以看出,如果要對容器做定製化修改,根據第四步來編寫自定義類即可。
那麼,走到這裏,整個初始化就剩下refresh了,refresh裏面做的事情非常多,下一節再講