Spring 源碼閱讀 之 Spring框架加載

  提及第一次閱讀Spring Framework源碼,大概仍是2010年吧,那個時候還不懂技巧和方法,一頭紮在代碼的汪洋大海里,出不來了。後面幾年偶爾斷斷續續的也看過幾回,都是不得要領,最後都是無疾而終。因此以爲閱讀這種大型的代碼項目,很吃力,也很艱難,須要不斷的堅持。 web

  最近項目不是很忙,下班早,就又把Spring的源碼翻出來看看,也看了一段時間了,此次算是小有收穫吧,因而打算學博客記錄下來。 spring

  在這以前,並無打算在繼續寫博客,由於這裏的這種討厭的限制,並且也愈來愈不喜歡這裏的風格。可是有以爲,學過的東西,既然有價值,就記錄下來吧,好記性不如爛筆頭,不記得的時候能夠打開看看。在這以前,個人一些學習筆記一直是使用爲知筆記來記錄的,如今把記錄在爲知筆記裏的內容從新翻出來整理一下,帖子博客上吧。app

  好了,開始進入正題吧:
框架

----------------------------------------------------------------------------------------------------學習

  在使用Spring Framework的時候,各類教程都是介紹首先要在 web.xml 裏面配置一個 listener,該 listener 會在web容器啓動的時候依次加載,從而來加載Spring Framework。那就從這個 listener 開始閱讀吧。this

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  ContextLoaderListener 實現了 ServletContextListener 接口,所以 ContextLoaderListener 監聽了當前Web Application的生命週期,在 Web 容器啓動時會調用其實現的 contextInitialized() 方法來加載Spring Framework框架,當容器終止時會調用 其 contextDestroyed() 方法來銷燬資源。 ContextLoaderListener類的contextInitialized()方法是這樣實現的:spa

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    if (this.contextLoader == null) {
        this.contextLoader = this;
    }
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

  這個方法很簡單,可是我實在不明白框架的設計者爲何要先判斷 contextLoader 是否爲 null,而後再經過 contextLoader 來調用 initWebApplicationContext() 方法,而爲何不在 contextInitialized()方法裏直接調用 initWebApplicationContext() 方法,一行代碼就搞定的事,爲何還這麼麻煩? debug

  爲何會有這樣的疑問呢,由於initWebApplicationContext()方法是ContextLoader類的成員方法,而ContextLoaderListener 又繼承自 ContextLoader,initWebApplicationContext()方法是用public進行修飾的,所以該方法也獲得 ContextLoaderListener 繼承,也就是說ContextLoaderListener也擁有此方法。那麼做者爲何還要在該類裏增長一個ContextLoader類型的成員變量contextLoader,用 contextLoader 來調用 initWebApplicationContext()呢? 看了一下整個ContextLoaderListener類,contextLoader 都是調用其自身的方法,而這些方法也被ContextLoaderListener繼承了,實在是想不通。設計

  進入 initWebApplicationContext() 方法,該方法實現了整個Spring的加載。該方法除去log和try/catch代碼,真正的代碼也不是不少。外表看似簡單,可是裏面的邏輯實現,徹底超乎個人想象,直接看代碼吧: 
code

 1 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 2     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
 3         throw new IllegalStateException(
 4                 "Cannot initialize context because there is already a root application context present - " +
 5                 "check whether you have multiple ContextLoader* definitions in your web.xml!");
 6     }
 7 
 8     Log logger = LogFactory.getLog(ContextLoader.class);
 9     servletContext.log("Initializing Spring root WebApplicationContext");
10     if (logger.isInfoEnabled()) {
11         logger.info("Root WebApplicationContext: initialization started");
12     }
13     long startTime = System.currentTimeMillis();
14 
15     try {
16         // Store context in local instance variable, to guarantee that
17         // it is available on ServletContext shutdown.
18         if (this.context == null) {
19             this.context = createWebApplicationContext(servletContext);
20         }
21         if (this.context instanceof ConfigurableWebApplicationContext) {
22             configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
23         }
24         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
25 
26         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
27         if (ccl == ContextLoader.class.getClassLoader()) {
28             currentContext = this.context;
29         }
30         else if (ccl != null) {
31             currentContextPerThread.put(ccl, this.context);
32         }
33 
34         if (logger.isDebugEnabled()) {
35             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
36                     WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
37         }
38         if (logger.isInfoEnabled()) {
39             long elapsedTime = System.currentTimeMillis() - startTime;
40             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
41         }
42 
43         return this.context;
44     }
45     catch (RuntimeException ex) {
46         logger.error("Context initialization failed", ex);
47         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
48         throw ex;
49     }
50     catch (Error err) {
51         logger.error("Context initialization failed", err);
52         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
53         throw err;
54     }
55 }

    代碼第二行,從 servletContext取出以 org.springframework.web.context.WebApplicationContext.ROOT 爲key的對象,若是該對象不等於null,說明當前servletContext已經加載成功了Spring,則直接拋出異常。爲何要作此判斷呢?難道是當前Web容器啓動了多個Web應用,每一個應用都使用了Spring框架框架嗎?若是是這樣,則每一個Web應用都對應着一個單獨的ServletContext,是不會出現這種問題的,貌似也就只又在 web.xml裏面配置多個 相似的加載 Spring Framework的listener纔會觸發此問題。好了,那 org.springframework.web.context.WebApplicationContext.ROOT 又是在什麼時候放進 servletContext 的呢? 在第24行的地方,當代碼執行到第24行的地方時,說明整個Spring框架已經加載並初始化完畢。   

    代碼第29行,調用 createWebApplicationContext() 方法建立Spring Web應用上下文。Spring Web應用上下文是怎麼建立的呢?下文在繼續。。。

相關文章
相關標籤/搜索