Spring-web源碼解析之ContextLoader

基於版本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裏面做的事情非常多,下一節再講



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章