關(guān)于IoC容器和控制反轉(zhuǎn)(也被稱為依賴注入)模式以及Spring IoC的應(yīng)用場景我在這里就不進(jìn)行贅述了,下面直接深入到Spring的源碼當(dāng)中來探究一下IoC容器究竟是如何工作的。
在文章開頭,我們要明確兩個概念:
- IoC容器的創(chuàng)建就是我們創(chuàng)建一個容器,使其擁有IoC容器的基本結(jié)構(gòu);(下文中提到的的createWebApplicationContext完成的是IoC容器的創(chuàng)建工作)
- 在完成IoC容器創(chuàng)建的前提下進(jìn)行bean的注冊以及依賴注入之后才算完成了IoC容器的初始化(下文中提到的ConfigureAndRefreshWebApplicationContext進(jìn)行的是IoC容器的初始化工作但是關(guān)于容器初始化的細(xì)節(jié)我將放到后面的博客進(jìn)行介紹)。
在Spring IoC容器系列的設(shè)計(jì)中,我們可以看到兩個兩個主要的容器系列:一個是實(shí)現(xiàn)BeanFactory接口的簡單容器系列,這個系列容器只實(shí)現(xiàn)了容器的最基本功能;另一個是ApplicationContext應(yīng)用上下文,它作為容器的高級形態(tài)而存在。應(yīng)用上下文在簡單容器的基礎(chǔ)上,增加了許多面向框架的特性,同時對應(yīng)用環(huán)境作了許多適配。有了這兩種基本的容器系列,基本上可以滿足用戶對IoC容器使用的大部分需求了。下面我們主要來看看IoC容器在Web容器中是如何進(jìn)行初始化的。
既然我們要探究IoC容器初始化的過程,那就要問一個問題:
IoC容器什么時候會進(jìn)行初始化的行為呢?
我們先來看一個非?;A(chǔ)的場景:

上面是web.xml配置文件中的一部分
- contextConfigLocation對應(yīng)的value是Spring配置文件的絕對路徑;
- 下面這個監(jiān)聽器主要用來對Servlet容器(在這里指的是Tomcat)的行為進(jìn)行監(jiān)聽
我們先來看看監(jiān)聽器類ContextLoaderListener中有什么東西:

我們可以發(fā)現(xiàn)ContextLoaderListener繼承自ContextLoader,并且還實(shí)現(xiàn)了ServletContextListener。并且它的構(gòu)造函數(shù)中傳入了一個WebApplicationContext,它是繼承自ApplicationContext接口的高級IoC容器。

ServletContextListener是Servlet中比較重要的一個接口:它的作用是用來監(jiān)聽Servlet容器的啟動和銷毀事件。所以在ContextLoaderListener中:
- contextInitialized方法的入?yún)⒒蚴潜O(jiān)聽的Event是ServletContextEvent事件,也就是Tomcat啟動加載完web.xml會產(chǎn)生的事件,ServletContextEvent持有了從web.xml加載的初始化配置的ServletContext上下文。
- ContextDestroyed方法的入?yún)⒒蚴潜O(jiān)聽的Event是ServletContextEvent事件,在Tomcat關(guān)閉的時候執(zhí)行該方法。
所以我們現(xiàn)在可以先捋一下流程:當(dāng)Servlet容器啟動事件發(fā)生時,將被ContextLoaderListen監(jiān)聽器監(jiān)聽到。此時ContextLoaderListener會調(diào)用實(shí)現(xiàn)ServletContextListener接口后實(shí)現(xiàn)的contextInitialized方法,并把在web.xml加載初始化后獲取的ServletContext傳入initWebApplicationContext函數(shù)中進(jìn)行IoC容器的初始化。
因?yàn)閕nitWebApplicationContext函數(shù)是從ContextLoader繼承過來的,所以我們現(xiàn)在進(jìn)入ContextLoader源碼中看一看。

映入眼簾的是個靜態(tài)代碼塊:
- 創(chuàng)建一個ClassPathResource對象,同時把值為"ContextLoader.properties"的一個常量作為參數(shù)傳入。易知ContextLoader.properties文件與ContextLoader類是在同一個目錄下的;ContextLoader.properties文件內(nèi)容如下:
org.springframework.web.context.WebApplicationContext=
org.springframework.web.context.support.XmlWebApplicationContext
因此我們可以得知Spring默認(rèn)初始化的是XmlWebApplicationContext容器
- 得到一個Properties對象,后面講根據(jù)類名來創(chuàng)建對應(yīng)的ApplicationContext容器
下面來看看initiWebApplicationContext方法


我們現(xiàn)在可以接著剛才的流程繼續(xù)講下去:當(dāng)調(diào)用ContextLoaderListener中的initWebApplicationContext的函數(shù)并且將獲取到的servletContext作為參數(shù)傳入之后,initWebApplicationContext首先會嘗試從servletContext中獲取根容器,如果容器不為空,則容器初始化失敗---因?yàn)閣eb.xml中可能定義了多個IoC容器的加載器。假如此時容器還未初始化,則調(diào)用createWebApplicationContext方法來創(chuàng)建一個容器。創(chuàng)建完容器之后,將會調(diào)用一個非常重要的configureAndRefreshWebApplicationContext方法。在執(zhí)行這個方法的時候,會將從ApplicationContext.xml配置文件中獲取到的內(nèi)容配置到已經(jīng)創(chuàng)建好了的XmlWebApplicationContext容器中去,并調(diào)用refresh方法來完成容器的初始化。然后,再將已經(jīng)完成初始化的XmlWebApplicationContext容器注冊到servletContext中去。
其實(shí)在Web容器中,ServletContext為Spring的IoC容器提供了宿主環(huán)境,對應(yīng)的建立起一個IoC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業(yè)務(wù)對象、數(shù)據(jù)存取對象、資源、事務(wù)管理器等各種中間層對象。在這個上下文的基礎(chǔ)上,與Web MVC相關(guān)還會有一個上下文來保持控制器之類的MVC對象,這樣就構(gòu)成了一個層次化的上下文結(jié)構(gòu)。因?yàn)樵趇nitWebApplicationContext方法中我們可以看到其實(shí)創(chuàng)建ApplicationContext容器的工作是交由createWebApplicationContext方法來實(shí)現(xiàn)的,下面我們來看看這個方法:

createWebApplicationContext函數(shù)功能:
- 決定要創(chuàng)建的ApplicationContext類型;
- 實(shí)例化一個ApplicationContext
那么它是如何決定要創(chuàng)建的ApplicationContext類型的呢?
起作用的其實(shí)是方法中第一行的determineContextClass方法

完成了IoC容器的創(chuàng)建之后,在initWebApplicationContext中講調(diào)用configureAndRefreshWebApplicationContext來對該IoC進(jìn)行初始化:
- 為創(chuàng)建好的IoC容器設(shè)置Web應(yīng)用的上下文,以便二者整合;
- 為同一個IoC容器設(shè)置配置文件的絕對路徑;
- 調(diào)用IoC容器的refresh函數(shù)對其進(jìn)行初始化

由于IoC容器的初始化涉及到比較多的步驟:包括BeanDefinition的Resource定位等等,所以我將在后面另開博客進(jìn)行介紹。