Spring原始碼分析(二)(IoC容器的實現)(1)
Ioc(Inversion of Control)——“控制反轉”,不是什麼技術,而是一種設計思想。在Java開發中,Ioc意味著將你設計好的物件交給容器控制,而不是傳統的在你的物件內部直接控制。理解好Ioc的關鍵是要明確“誰控制誰,控制什麼,為何是反轉(有反轉就應該有正轉了),哪些方面反轉了”,那我們來深入分析一下:
●誰控制誰,控制什麼:傳統Java SE程式設計,我們直接在物件內部通過new進行建立物件,是程式主動去建立依賴物件;而IoC是有專門一個容器來建立這些物件,即由Ioc容器來控制物件的建立;誰控制誰?當然是IoC 容器控制了物件;控制什麼?那就是主要控制了外部資源獲取
●為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在物件中主動控制去直接獲取依賴物件,也就是正轉;而反轉則是由容器來幫忙建立及注入依賴物件;為何是反轉?因為由容器幫我們查詢及注入依賴物件,物件只是被動的接受依賴物件,所以是反轉;哪些方面反轉了?依賴物件的獲取被反轉了。
用圖例說明一下,傳統程式設計如圖2-1,都是主動去建立相關物件然後再組合起來:
圖2-1 傳統應用程式示意圖
當有了IoC/DI的容器後,在客戶端類中不再主動去建立這些物件了,如圖2-2所示:
圖2-2有IoC/DI容器後程序結構示意圖
IoC能做什麼:
IoC和DI:DI—Dependency Injection,即“依賴注入”:是元件之間依賴關係由容器在執行期決定,形象的說,即由容器動態的將某個依賴關係注入到元件之中。依賴注入的目的並非為軟體系統帶來更多功能,而是為了提升元件重用的頻率,併為系統搭建一個靈活、可擴充套件的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何程式碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰注入誰,注入了什麼”,那我們來深入分析一下:
●誰依賴於誰:當然是應用程式依賴於IoC容器;
●為什麼需要依賴:應用程式需要IoC容器來提供物件需要的外部資源;
●誰注入誰:很明顯是IoC容器注入應用程式某個物件,應用程式依賴的物件;
●注入了什麼:就是注入某個物件所需要的外部資源(包括物件、資源、常量資料)。
IoC和DI由什麼關係呢?其實它們是同一個概念的不同角度描述,由於控制反轉概念比較含糊(可能只是理解為容器控制物件這一個層面,很難讓人想到誰來維護物件關係),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,“依賴注入”明確描述了“被注入物件依賴IoC容器配置依賴物件”。
原文:http://sishuok.com/forum/blogPost/list/2427.html
更多請參考:https://www.martinfowler.com/articles/injection.html
OK下面進入正題
IoC容器系列的設計與實現:BeanFactory和ApplicationContext
在 Spring IoC容器的設計中 ,有兩個主要的容器系列 ,一個是實現BeanFactory介面的簡單容器系列,這系列容器只實現了容器的最基本功能 ,另一個是ApplicalionContext應用上下文,它作為容器的高階形態而存在,在簡單容器的基礎上,增加了許多面向框架的特性,同時對應用環境作了許多適配。
1.從介面BeanFactory到HiearerchialBeanFactory,再到ConfigurableBeanFactory,是一條主要的BeanFactory設計路徑。
在這條介面設計路徑中,BeanFactory介面定義了基本的IOC容器規範。在這個介面定義中,BeanFactory包括了getBean()這樣的IoC容器的基本方法(通過這個方法可以從容器中獲取Bean)。而HierarchicalBeanFactory介面在繼承了BeanFactory的基本介面之後,增加了getParentBeanFactory()的介面功能,使得BeanFactory具備了雙親IoC容器的管理功能。
下面的ConfigurableBeanFactory主要定義了對BeanFactory的配置功能,比如setParentBeanFactory()設定雙親IoC容器,通過addBeanPostProcessor()配置Bean的後置處理器等。通過這些介面設計的疊加,定義了BeanFactory最簡單的IOC容器的基本功能。
2.第二條設計主線是:以ApplicationContext應用上下文介面為核心的介面設計。
這裡涉及的主要設計介面有,從BeanFactory到ListableBeanFactory,再到ApplicationContext,再到常用的WebApplicationContext或者ConfigurableApplicationContext介面。我們常用的應用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的實現。
在這個介面體系中,ListableBeanFactory和HierarchicalBeanFactory兩個介面,連線BeanFactory介面定義和ApplicationContext應用上下文介面定義。
在ListableBeanFactory介面中,細化了許多BeanFactory的介面功能,比如定義了setBeanDefinitionNames介面方法;
對於ApplicationContext介面,它通過繼承了MessageSource、ResourceLoader、ApplicationEventPublisher介面,在BeanFactory簡單Ioc容器的基礎上添加了許多對高階容器的特性的支援。
3.圖中涉及的是主要的介面關係,而具體的Ioc容器都是在這個介面體系下實現的,比如DefaultListableBeanFactory,這個基本Ioc容器的實現就是實現了ConfigurableBeanFactory,從而成為了一個簡單Ioc容器的實現。像其他的Ioc容器,比如XMLBeanFactory,都是在DefaultListableBeanFactory的基礎上做擴充套件。同樣,ApplicationContext的實現也是如此。
4.這個介面系統是以BeanFactory和ApplicationContext為核心的,而BeanFactory又是Ioc容器的最基本的介面,在ApplicationContext的設計中,一方面,可以看到他繼承了HierarchicalBeanFactory等BeanFactory的介面,具備了BeanFactory Ioc容器的基本功能,另外一方面,通過繼承MessageSource、ResourceLoader、ApplicationEventPublisher這些介面,BeanFactory為ApplicationContext賦予了更高階的Ioc容器特性。對於ApplicationContext而言,為了在Web環境中使用它,還設計了WebApplicationContext介面,而這個介面通過繼承ThemeSource介面來擴充功能。
下面通過XmlBeanFactory原始碼看一下基本的IoC容器的實現:
XmlBeanFactory繼承自DefaultListableBeanFactory,DefaultListableBeanFactory這個類十分重要,是經常用到的一個IoC容器的實現,如在設計ApplicationContext時也用到了。DefaultListableBeanFactory包含了基本IoC容器所具有的重要功能。
在XmlBeanFactory中初始化了一個XmlBeanDefinitionReader物件,通過這個物件那些以XML方式定義的BeanDefinition就有了處理的地方。
public class XmlBeanFactory extends DefaultListableBeanFactory {
//XML Bean定義的Bean定義閱讀器。將實際的XML文件讀取委託給BeanDefinitionDocumentReader介面的實現。
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);//Resource是Spring用來封裝I/O的操作類
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
以程式設計的方式進行實現:
ClassPathResource res=new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
這就是通過factory物件來使用DefaultListableBeanFactory這個IoC容器。其步驟:
- 建立IoC配置檔案的抽象資源,這個資源包含了BenaDefinition的定義資訊。(BeanDefinition描述了bean例項,它具有屬性值、建構函式引數值和具體實現提供的進一步資訊。)
- 建立BeanFactory,這裡用DefaultListableBeanFactory。
- 建立載入BeanDefinition的讀取器,這裡用XmlBeanDefinitionReader來載入XML檔案形式的BeanDefinition,通過一個回撥配置給BeanFactory。
- 從定義好的資源位置讀入配置資訊,具體解析由XmlBeanDefinitionReader來完成。完成整個載入和註冊後IoC容器就建立起來了,這時候可以直接使用IoC容器。
ok下面看一下ApplicationContext容器的設計原理,這裡用FileSystemXmlApplicationContext來說明:
在FileSystemXmlApplicationContext的設計中,我們看到ApplicationContext應用上下文的主要功能已經在FileSystemXmlApplicationContext的基類AbstractXmlApplication中實現了,在FileSystemXmlApplicationContext中,作為一個具體的應用上下文,只需要實現和它自身設計相關的兩個功能。
1.如果應用直接使用FileSystemXmlApplicationContext,對於例項化這個應用上下文的支援,同時啟動IoC容器的refresh()過程
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);//設定此應用程式上下文的配置位置。如果沒有設定,實現可能會在適當的時候使用預設值。
if (refresh) {
refresh();//AbstractApplicationContext的refresh
}
}
這個refresh()過程會牽涉IoC容器啟動的一系列複雜操作,同時,對於不同的容器實現,這些操作都是類似的,因此在基類中將它們封裝好。所以,我們在FileSystemXmlApplicationContext的設計中看到的只是一個簡單的呼叫。
2.與FileSystemXmlApplicationContext設計具體相關的功能,這部分與怎樣從檔案系統中載入XML的Bean定義資源有關,通過這個過程可以為在檔案系統中讀取以XML形式存在的BeanDefinition做準備,因為不同的應用上下文實現對應不同的讀取BeanDefinition的方式。
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);//獲取資源的定位
}
通過呼叫這個方法可以得到FileSystemResource的資源定位。
參考《SPRING技術內幕》