關于IoC容器和控制反轉(也被稱為依賴注入)模式以及Spring IoC的應用場景我在這里就不進行贅述了,下面直接深入到Spring的源碼當中來探究一下IoC容器究竟是如何工作的。
在文章開頭,我們要明確兩個概念:
- IoC容器的創建就是我們創建一個容器,使其擁有IoC容器的基本結構;(下文中提到的的createWebApplicationContext完成的是IoC容器的創建工作)
- 在完成IoC容器創建的前提下進行bean的注冊以及依賴注入之后才算完成了IoC容器的初始化(下文中提到的ConfigureAndRefreshWebApplicationContext進行的是IoC容器的初始化工作但是關于容器初始化的細節我將放到后面的博客進行介紹)。
在Spring IoC容器系列的設計中,我們可以看到兩個兩個主要的容器系列:一個是實現BeanFactory接口的簡單容器系列,這個系列容器只實現了容器的最基本功能;另一個是ApplicationContext應用上下文,它作為容器的高級形態而存在。應用上下文在簡單容器的基礎上,增加了許多面向框架的特性,同時對應用環境作了許多適配。有了這兩種基本的容器系列,基本上可以滿足用戶對IoC容器使用的大部分需求了。下面我們主要來看看IoC容器在Web容器中是如何進行初始化的。
既然我們要探究IoC容器初始化的過程,那就要問一個問題:
IoC容器什么時候會進行初始化的行為呢?
我們先來看一個非常基礎的場景:
上面是web.xml配置文件中的一部分
- contextConfigLocation對應的value是Spring配置文件的絕對路徑;
- 下面這個監聽器主要用來對Servlet容器(在這里指的是Tomcat)的行為進行監聽
我們先來看看監聽器類ContextLoaderListener中有什么東西:
我們可以發現ContextLoaderListener繼承自ContextLoader,并且還實現了ServletContextListener。并且它的構造函數中傳入了一個WebApplicationContext,它是繼承自ApplicationContext接口的高級IoC容器。
ServletContextListener是Servlet中比較重要的一個接口:它的作用是用來監聽Servlet容器的啟動和銷毀事件。所以在ContextLoaderListener中:
- contextInitialized方法的入參或是監聽的Event是ServletContextEvent事件,也就是Tomcat啟動加載完web.xml會產生的事件,ServletContextEvent持有了從web.xml加載的初始化配置的ServletContext上下文。
- ContextDestroyed方法的入參或是監聽的Event是ServletContextEvent事件,在Tomcat關閉的時候執行該方法。
所以我們現在可以先捋一下流程:當Servlet容器啟動事件發生時,將被ContextLoaderListen監聽器監聽到。此時ContextLoaderListener會調用實現ServletContextListener接口后實現的contextInitialized方法,并把在web.xml加載初始化后獲取的ServletContext傳入initWebApplicationContext函數中進行IoC容器的初始化。
因為initWebApplicationContext函數是從ContextLoader繼承過來的,所以我們現在進入ContextLoader源碼中看一看。
映入眼簾的是個靜態代碼塊:
- 創建一個ClassPathResource對象,同時把值為"ContextLoader.properties"的一個常量作為參數傳入。易知ContextLoader.properties文件與ContextLoader類是在同一個目錄下的;ContextLoader.properties文件內容如下:
org.springframework.web.context.WebApplicationContext=
org.springframework.web.context.support.XmlWebApplicationContext
因此我們可以得知Spring默認初始化的是XmlWebApplicationContext容器
- 得到一個Properties對象,后面講根據類名來創建對應的ApplicationContext容器
下面來看看initiWebApplicationContext方法
我們現在可以接著剛才的流程繼續講下去:當調用ContextLoaderListener中的initWebApplicationContext的函數并且將獲取到的servletContext作為參數傳入之后,initWebApplicationContext首先會嘗試從servletContext中獲取根容器,如果容器不為空,則容器初始化失敗---因為web.xml中可能定義了多個IoC容器的加載器。假如此時容器還未初始化,則調用createWebApplicationContext方法來創建一個容器。創建完容器之后,將會調用一個非常重要的configureAndRefreshWebApplicationContext方法。在執行這個方法的時候,會將從ApplicationContext.xml配置文件中獲取到的內容配置到已經創建好了的XmlWebApplicationContext容器中去,并調用refresh方法來完成容器的初始化。然后,再將已經完成初始化的XmlWebApplicationContext容器注冊到servletContext中去。
其實在Web容器中,ServletContext為Spring的IoC容器提供了宿主環境,對應的建立起一個IoC容器的體系。其中,首先需要建立的是根上下文,這個上下文持有的對象可以有業務對象、數據存取對象、資源、事務管理器等各種中間層對象。在這個上下文的基礎上,與Web MVC相關還會有一個上下文來保持控制器之類的MVC對象,這樣就構成了一個層次化的上下文結構。因為在initWebApplicationContext方法中我們可以看到其實創建ApplicationContext容器的工作是交由createWebApplicationContext方法來實現的,下面我們來看看這個方法:
createWebApplicationContext函數功能:
- 決定要創建的ApplicationContext類型;
- 實例化一個ApplicationContext
那么它是如何決定要創建的ApplicationContext類型的呢?
起作用的其實是方法中第一行的determineContextClass方法
完成了IoC容器的創建之后,在initWebApplicationContext中講調用configureAndRefreshWebApplicationContext來對該IoC進行初始化:
- 為創建好的IoC容器設置Web應用的上下文,以便二者整合;
- 為同一個IoC容器設置配置文件的絕對路徑;
- 調用IoC容器的refresh函數對其進行初始化
由于IoC容器的初始化涉及到比較多的步驟:包括BeanDefinition的Resource定位等等,所以我將在后面另開博客進行介紹。