1. 程式人生 > >Spring原始碼分析(二)(IoC容器的實現)(1)

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不是一種技術,只是一種思想,一個重要的面向物件程式設計的法則,它能指導我們如何設計出鬆耦合、更優良的程式。傳統應用程式都是由我們在類內部主動建立依賴物件,從而導致類與類之間高耦合,難於測試;有了IoC容器後,把建立和查詢依賴物件的控制權交給了容器,由容器進行注入組合物件,所以物件與物件之間是鬆散耦合,這樣也方便測試,利於功能複用,更重要的是使得程式的整個體系結構變得非常靈活。IoC很好的體現了面向物件設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由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容器。其步驟:

  1. 建立IoC配置檔案的抽象資源,這個資源包含了BenaDefinition的定義資訊。(BeanDefinition描述了bean例項,它具有屬性值、建構函式引數值和具體實現提供的進一步資訊。)
  2. 建立BeanFactory,這裡用DefaultListableBeanFactory。
  3. 建立載入BeanDefinition的讀取器,這裡用XmlBeanDefinitionReader來載入XML檔案形式的BeanDefinition,通過一個回撥配置給BeanFactory。
  4. 從定義好的資源位置讀入配置資訊,具體解析由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技術內幕》