提及第一次閱讀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應用上下文是怎麼建立的呢?下文在繼續。。。