1 IoC 容器概述
1.1 IOC 容器和依賴反轉模式
我們日常的 java 項目開發都是由兩個或多個類的彼此合作來實現業務邏輯的,這使得每個對象都需要與其合作的對象的引用(稱為所依賴的對象),如果合作的對象的引用或依賴關系由具體的對象來實現,這對復雜的面向對象系統的設計與開發是非常不利的,由此,我們如果能把這些依賴關系和對象的注入交給框架來實現,讓具體對象交出手中對于依賴對象的控制,那么就能很大程度上解耦代碼,這顯然是極有價值的。而這,就是“依賴反轉”,即反轉對依賴的控制,把控制權從具體的對象中轉交到平臺或者框架。
實現依賴反轉的實現有很多種,在 Spring 中,IoC 容器就是實現這個模式的載體,它可以在對象生成或初始化的過程中,直接將數據或者依賴對象的引用注入到對象的數據域中從而來實現方法調用的依賴。而這種依賴注入是遞歸的,依賴對象會被逐層注入,從而建立起一套有序的對象依賴關系,簡化了對象依賴關系的管理,把面向對象過程中需要執行的如何新建對象、為對象引用賦值等操作交由容器統一管理,極大程度上減低了面向對象編程的復雜性。
1.2 IoC 在 Spring 中的應用場景
在 Spring 中,Spring IoC 提供了一個基本 JavaBean 容器,通過 IoC 模式管理依賴關系,并通過依賴注入和 AOP 切面增強了為 JavaBean 這樣的 POJO 對象提供了事務管理、聲明周期管理等功能。
在應用開發中,當我們在設計組件時,往往需要引入和調用其他組件的服務時,這種依賴關系如果固化在組件設計中就會導致組件之間的耦合和維護難度的增大,這個時候如果使用 IoC 容器,把資源獲取的方式反轉,讓 IoC 容器主動管理這些依賴關系,將依賴關系注入到組件中,那么這些依賴關系的適配和管理就會更加靈活。
在應用管理依賴關系時,如果在 IOC 實現依賴反轉的過程中,能通過可視化的文本來完成配置,并且通過工具對這些配置信息進行可視化的管理和瀏覽,那么肯定能提高依賴關系的管理水平,而且如果耦合關系變動,并不需要重新修改和編譯 Java 代碼,這符合在面向對象過程中的開閉原則。
2 IoC 容器系列的設計與實現:BeanFactory 和 ApplicationContext
使用過 Spring 的同學應該都接觸過 BeanFactory 和 ApplicationContext,其實它們就可以看做 Spring IoC 容器的具體表現形式,了解這兩者的區別與聯系對于我們理解和使用 IoC 容器是比較重要的。我們先來看一下 IoC 容器的接口設計圖:
下面我們來對這張接口設計圖做一下解析:
第一條接口設計主線是:BeanFactory -> HierarchicalBeanFactory -> ConfigureBeanFactory。在這條設計路徑中,BeanFactory 定義了 IoC 容器的基本規范,包括了例如 getBean()(從容器中取得 Bean)這樣的 IoC 容器基本方法。而 HierarchicalBeanFactory 接口則在繼承 BeanFactory 基礎上,增加了 getParentBeanFactory() 的接口功能,使得 BeanFactory 具備了雙親 IoC容器的管理功能。接下來的 ConfigureBeanFactory 接口中,主要定義了一些對 BeanFactory 的配置功能,如通過 setParentBeanFactory() 設置雙親容器和通過 addBeanPostProcessor() 設置 Bean 后置處理器等。通過這些接口的疊加,定義了 BeanFactory 就是簡單 IoC 容器的基本功能。
第二條設計主線是以 ApplicationContext 為核心的路徑:BeanFactory -> ListableBeanFactory -> ApplicationContext 再到我們常用的 WebApplicationContext 或 ConfigureApplicationContext。在這條路徑中,ListableBeanFactory 和 HierarchicalBeanFactory 連接了 BeanFactory 接口和 ApplicationContext 應用上下文,ListableBeanFactory 細化了 BeanFactory 接口功能,HierarchicalBeanFactory 上文已經提到了。對于 ApplicationContext,它通過集成 MessageSource、ResourceLoader、ApplicationEventPublisher 接口,在 BeanFactory 簡單的 IoC 容器的基礎上添加了許多對高級容器的特性和支持。
這里主要涉及的是接口關系,而具體的容器是在這個接口體系下實現的,必須 DefaultListableBeanFactory 這個基本的 IoC 容器就實現了 ConfigureListableBeanFactory,從而成為一個簡單的 IoC 容器的實現。而其他的 BeanFactory 必須 XmlBeanFactory 都是在 DefaultListableBeanFactory 的基礎上做擴展,同樣的 ApplicationContext 體系也是一樣。
我們通過以上的接口設計圖跟分析可以看出,整個 Spring IoC 容器就是以 BeanFactory 和 ApplicationContext 作為核心的。BeanFactory 定義了 IoC 容器的基本功能,而 ApplicationContext 體系則在 BeanFactory 基礎上通過繼承其他接口來實現高級容器特征。下面我們來看一下這兩個體系的應用場景:
-
BeanFactory 的應用場景
- BeanFactory 提供的是最基本的 IoC 容器的功能,關于這些功能,我們可以在接口中看到:
BeanFactory接口 - BeanFactory 接口定義了 IoC 容器最基本的形式,并且提供了 IoC 容器所應該遵循的最基本服務契約,同時也是我們使用 IoC 容器所應該遵守的最底層和最基本的編程規范,這些接口方法定義勾畫出了 IoC 容器的基本輪廓。而在 Spring 的的代碼實現中,BeanFactory 只是一個接口類,而后面的 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext 等都可以看成是容器附加了某種功能的實現。
- BeanFactory 中定義了 getBean 方法,其參數類型有 Bean 的名字和其他參數,這些都是對 IoC 容器中存在的 Bean 進行索引。同時還定義了其他方法,方法的目的可以通過名字很明顯的看出來,這里就不一一說明了。
- 可以看到,這里定義的只是接口方法,而這一系列的接口,使用不同的 Bean 的檢索方法,很方便的從 IoC 容器中得到所需要的 Bean,從這個角度來看,這些方法代表的是最基本的 IoC 容器入口。
- BeanFactory 提供的是最基本的 IoC 容器的功能,關于這些功能,我們可以在接口中看到:
-
BeanFacory 的設計原理
-
BeanFactory 接口提供了使用 IoC 容器的基本規范,而在這個規范之上, Spring 還提供了符合這個 IoC 容器接口的一系列容器的實現來供開發人員使用,下面我們以 XmlBeanFactory 為例來簡單說明一下 IoC 容器的實現原理。
XmlBeanFactory設計的類繼承關系 可以看到,作為最簡單 IoC 容器系列最底層實現的 XmlBeanFactory,與我們 Spring 應用中用到的那些上下文相比,有一個明顯的特點:它只提供最基本的 IoC 容器的功能,理解這一點有助于幫我我們理解 ApplicationContext 和 BeanFactory 之間的區別與聯系。
-
XmlBeanFactory 在繼承 DafaultListableBeanFactory 類的基礎上,新增了新的功能。從類的名字上可以看出應該是與讀取 xml 配置文件相關的 IoC 容器。那么該讀取過程是怎么實現的?
- 我們先來看一下 XmlBeanFactory 的源碼:
@Deprecated public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
- 可以看出,在 XmlBeanFactory 中,初始化了一個 XmlBeanDefinitionReader 對象,實際上,該對象就是處理以 XML 方式定義的 BeanDefinition 的地方。
- 在構造 XmlBeanFactory 這個 IoC 容器,需要指定 BeanDefinition 的信息來源,而這個來源需要封裝成 Spring 中的 Resource 類(Spring 用來封裝 I/O 操作的類)。將 Resource 作為構造參數傳遞給 XmlBeanFactory 構造函數。這樣, IoC 容器就可以方便的定位到需要的 BeanDefinition 信息來對 Bean 完成容器的初始化和依賴注入的過程。至于如何獲取 Resource 和將 Resouce 轉化為 BeanDefinition,這是我們后面要提的重點。
我們看到 XmlBeanFactory 使用 DefaultListableBeanFactory 作為基類,該類非常重要,是我們經常用到的一個 IoC 容器的實現,必須在設計應用上下文 ApplicationContext 就會用到它,我們可以看到這個類包含了基本 IoC 容器所具有的重要功能,也是在很多地方都會用到的容器系列中的一個基本產品,其他很多的 IoC 容器都是通過持有和擴展 DefaultListableBeanFactory 來獲得特性功能的 IoC 容器的。
-
參考 XmlBeanFactory 的實現,我們使用編程的方式來使用 DefaultListableBeanFactory。從中我們可以看到 IoC 容器使用的一些基本過程,盡管實際開發中我們很少用到這種方式,但這對于我們理解 IoC 容器的工作原理是非常有幫助的。因為在這個編程式使用容器的過程中,很清楚的揭示了 IoC 容器實現中的那些關鍵類(Resource、DefaultListableBeanFactory、BeanDefinitionReader)之間的相互關系:
// 編程式使用容器的代碼: ClassPathResource res = new ClassPathResource("bean.xml") ; DefaultListableBeanFactory factory = new DefaultListableBeanFactory() ; XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory) ; reader.loadBeanDefinitions(res) ;
通過上面的代碼,我們可以看到使用 IoC 容器大概有以下四個步驟:
- 創建 IoC 配置文件的 Resource 資源,這個資源包含了 BeanDefinition 的定義信息。
- 創建一個 BeanFactory,這里使用 DefaultListableBeanFactory。
- 創建一個載入 BeanDefinition 的解讀器,這里使用 XmlBeanDefinitionReader 來載入 XML 文件形式 BeanDefinition,通過一個回調配置給 factory。
- 從定義好的資源位置讀入配置信息,具體的解析過程由 XmlBeanDefinitionReader 的 loadBeanDefinitions() 方法來完成。完成整個載入和注冊 Bean 定義之后,需要的 IoC 容器就建立起來可以直接使用了。
-
-
ApplicationContext 的應用場景:
-
在 Spring 中,系統已經為用戶提供了很多定義好的容器實現。而相比那些簡單擴展 BeanFactory 的基本 IoC 容器,開發人員常使用的 ApplicationContext 除了能夠提供前面容器介紹的基本功能之外,還為用戶提供了以下附加服務,可以讓用戶更加方便的使用。所以說 ApplicationContext 是一個高級形態的 IoC 容器,如下圖所示可以看到 ApplicationContext 在 BeanFactory 的基礎上所提供的附加服務:
ApplicationContext 類繼承關系- 支持不同的信息源。我們看到 ApplicationContext 擴展了 MessageSource 接口,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務。
- 訪問資源。這一點體現在對 ResourcePatternResolver 的繼承上。這樣我們可以從不同的地方得到 Bean 定義資源。這種抽象使用戶程序可以靈活地定義 Bean 信息,尤其是從不同的 I/O 途徑得到 Bean 的定義信息。關于 Resource 在 IoC 容器中的使用,我們后面會詳細介紹。
- 支持應用事件。繼承了接口 ApplicationEventPublisher,從而在上下文引入了事件機制,這些事件和 Bean 的生命周期的結合為 Bean 的管理提供了便利。
- 在 ApplicationContext 中提供的附加服務,使得基本 IoC 容器的功能更加豐富,對它的使用是一種面向框架的使用風格,所以建議在開發應用時使用 ApplicationContext 作為 IoC 容器的基本形式
-
-
ApplicationContext 的設計原理:
- 我們通過 FileSystemXmlApplicationContext 為例子來分析 ApplicationContext 的設計原理。
- 作為具體的應用上下文,我們只需要關注和它自身設計相關的功能,而其主要功能已經在其基類 AbstractXmlApplicationContext 中實現了。
- 一個功能是啟動 IoC 容器的 refresh() 過程:
這個 refresh() 涉及到 IoC 容器啟動時候一系列復雜的操作,而不同的容器,其操作都是類似的,因此將其封裝在基類 AbstractApplicationContext 中。在 FileSystemXmlApplicationContext 中僅僅涉及到簡單的調用而已。關于 refresh() 在 IoC 容器啟動時的表現,該方法可以說是所有 IoC 容器的入口,這是一個很重要的方法,我們將在后面具體分析。public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
- 另外一個功能是與這個獨特的應用上下文的設計有關,即從文件系統中加載 XML 的 Bean 定義資源。
可以看到,通過調用這個方法,可以得到 FileSystemResouce 的資源定位。protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
到此為止,我們大概了解了 IoC 容器的概念跟 Spring 中對于 IoC 容器的設計跟應用,之后我們要詳細的分析 IoC 容器的初始化、依賴注入跟其他相關特征的設計與實現等。