天天看點

《Spring技術内幕》——2.4節IoC容器的依賴注入

2.4 ioc容器的依賴注入

上面對ioc容器的初始化過程進行了詳細的分析,這個初始化過程完成的主要工作是在ioc容器中建立beandefinition資料映射。在此過程中并沒有看到ioc容器對bean依賴關系進行注入,接下來分析一下ioc容器是怎樣對bean的依賴關系進行注入的。

假設目前ioc容器已經載入了使用者定義的bean資訊,開始分析依賴注入的原理。首先,注意到依賴注入的過程是使用者第一次向ioc容器索要bean時觸發的,當然也有例外,也就是我們可以在beandefinition資訊中通過控制lazy-init屬性來讓容器完成對bean的預執行個體化。這個預執行個體化實際上也是一個完成依賴注入的過程,但它是在初始化的過程中完成的,稍後我們會詳細分析這個預執行個體化的處理。當使用者向ioc容器索要bean時,如果讀者還有印象,那麼一定還記得在基本的ioc容器接口beanfactory中,有一個getbean的接口定義,這個接口的實作就是觸發依賴注入發生的地方。為了進一步了解這個依賴注入過程的實作,下面從defaultlistablebeanfactory的基類abstractbeanfactory入手去看看getbean的實作,如代碼清單2-22所示。

代碼清單2-22 getbean觸發的依賴注入

這個就是依賴注入的入口,在這裡觸發了依賴注入,而依賴注入的發生是在容器中的beandefinition資料已經建立好的前提下進行的。“程式=資料+算法,”很經典的一句話,前面的beandefinition就是資料,下面看看這些資料是怎樣為依賴注入服務的。雖然依賴注入的過程不涉及複雜的算法問題,但這個過程也不簡單,因為我們都知道,對于ioc容器的使用,spring提供了許多的參數配置,每一個參數配置實際上代表了一個ioc容器的實作特性,這些特性的實作很多都需要在依賴注入的過程中或者對bean進行生命周期管理的過程中完成。盡管可以用最簡單的方式來描述ioc容器,将它視為一個hashmap,但隻能說這個hashmap是容器的最基本的資料結構,而不是ioc容器的全部。spring ioc容器作為一個産品,其價值展現在一系列相關的産品特性上,這些産品特性以依賴反轉模式的實作為核心,為使用者更好地使用依賴反轉提供便利,進而實作了一個完整的ioc容器産品。這些産品特性的實作并不是一個簡單的過程,它提供了一個成熟的ioc容器産品供使用者使用。是以,盡管spring ioc容器沒有什麼獨特的算法,但卻可以看成是一個成功的軟體工程産品,有許多值得我們學習的地方。

關于這個依賴注入的詳細過程會在下面進行分析

重點來說,getbean是依賴注入的起點,之後會調用createbean,下面通過createbean代碼來了解這個實作過程。在這個過程中,bean對象會依據beandefinition定義的要求生成。在abstractautowirecapablebeanfactory中實作了這個createbean,createbean不但生成了需要的bean,還對bean初始化進行了處理,比如實作了在beandefinition中的init-method屬性定義,bean後置處理器等。具體的過程如代碼清單2-23所示。

代碼清單2-23 abstractautowirecapablebeanfactory中的createbean

這裡用cglib對bean進行執行個體化。cglib是一個常用的位元組碼生成器的類庫,它提供了一系列的api來提供生成和轉換java的位元組碼的功能。在spring aop中也使用cglib對java的位元組碼進行增強。在ioc容器中,要了解怎樣使用cglib來生成bean對象,需要看一下simpleinstantiationstrategy類。這個strategy是spring用來生成bean對象的預設類,它提供了兩種執行個體化java對象的方法,一種是通過beanutils,它使用了jvm的反射功能,一種是通過前面提到的cglib來生成,如代碼清單2-25所示。

代碼清單2-25 使用simpleinstantiationstrategy生成java對象

到這裡已經分析了執行個體化bean對象的整個過程。在執行個體化bean對象生成的基礎上,再介紹一下spring是怎樣對這些對象進行處理的,也就是bean對象生成以後,怎樣把這些bean對象的依賴關系設定好,完成整個依賴注入過程。這個過程涉及對各種bean對象的屬性的處理過程(即依賴關系處理的過程),這些依賴關系處理的依據就是已經解析得到的beandefinition。要詳細了解這個過程,需要回到前面的populatebean方法,這個方法在abstractautowirecapablebeanfactory中的實作如代碼清單2-27所示。

代碼清單2-27 populatebean的實作

這裡通過使用beandefinitionresolver來對beandefinition進行解析,然後注入到property中。下面到beandefinitionvalueresolver中去看一下解析過程的實作,以對bean reference進行解析為例,如圖2-15所示,可以看到整個resolve的過程。具體的對bean reference進行解析的過程如代碼清單2-28所示。

圖2-15 resolve的調用過程

代碼清單2-28 對bean reference的解析

這樣就完成了對各種bean屬性的依賴注入過程。從代碼實作細節上看,對比spring 2.0的源代碼實作,spring 3.0的源代碼已經有了很大的改進,整個過程更為清晰了,特别是關于依賴注入的部分。如果讀者有興趣,可以比較一下spring 2.0和spring 3.0關于依賴注入部分的代碼實作,這樣可以更清晰地看到spring源代碼的演進過程,也可以看到spring團隊對代碼進行重構的思路。

在bean的建立和對象依賴注入的過程中,需要依據beandefinition中的資訊來遞歸地完成依賴注入。從上面的幾個遞歸過程中可以看到,這些遞歸都是以getbean為入口的。一個遞歸是在上下文體系中查找需要的bean和建立bean的遞歸調用;另一個遞歸是在依賴注入時,通過遞歸調用容器的getbean方法,得到目前bean的依賴bean,同時也觸發對依賴bean的建立和注入。在對bean的屬性進行依賴注入時,解析的過程也是一個遞歸的過程。這樣,根據依賴關系,一層一層地完成bean的建立和注入,直到最後完成目前bean的建立。有了這個頂層bean的建立和對它的屬性依賴注入的完成,意味着和目前bean相關的整個依賴鍊的注入也完成了。

在bean建立和依賴注入完成以後,在ioc容器中建立起一系列依靠依賴關系聯系起來的bean,這個bean已經不是簡單的java對象了。該bean系列以及bean之間的依賴關系建立完成以後,通過ioc容器的相關接口方法,就可以非常友善地供上層應用使用了。繼續以水桶為例,到這裡,我們不但找到了水源,而且成功地把水裝到了水桶中,同時對水桶裡的水完成了一系列的處理,比如消毒、煮沸……盡管還是水,但經過一系列的處理以後,這些水已經是開水了,可以直接飲用了。