騰訊必問的Spring IOC,要看看了!
原創(chuàng)【51CTO.com原創(chuàng)稿件】Java 作為流行的開發(fā)語(yǔ)言被廣大開發(fā)者所青睞,在 Java 平臺(tái)提供豐富的應(yīng)用程序開發(fā)功能的同時(shí),其存在的問題也暴露出來。
圖片來自包圖網(wǎng)
這個(gè)問題就是其缺乏將基礎(chǔ)組件構(gòu)建成完整系統(tǒng)的能力,因此開發(fā)者需要通過各種設(shè)計(jì)模式,將開發(fā)的組件進(jìn)行組合,從而構(gòu)建成最終的應(yīng)用。
為了解決這個(gè)問題,Spring 架構(gòu)推出了 IoC 組件,它可以通過正規(guī)化的方法來組合不同的組件,讓其成為完整的,可以用的應(yīng)用。
從此開發(fā)人員無須手動(dòng)設(shè)置對(duì)象的依賴關(guān)系,把這一工作交給了 Spring 容器去處理和管理,提升了開發(fā)體驗(yàn)。
今天將圍繞 Spring IoC 給大家講解其實(shí)現(xiàn)原理,接下來將會(huì)學(xué)到如下內(nèi)容:
- Spring IoC 的由來和概念
- Spring IoC 容器
- Spring IoC 的優(yōu)缺點(diǎn)
- IoC 與 DI
- DI 的自動(dòng)裝載
Spring IoC 的由來和概念
在介紹 Spring IoC 之前先來看看傳統(tǒng)的對(duì)象(組件)依賴是怎么做的,假設(shè)通過 RESTFUL 的方式訪問用戶信息(User)。
如圖 1 所示,用戶請(qǐng)求一個(gè) UserController 獲取 User 信息,UserController 會(huì)調(diào)用 UserService,在 UserService 中會(huì)處理關(guān)于 User 的業(yè)務(wù)邏輯。
圖 1:例子依賴關(guān)系
同時(shí) UserService 會(huì)調(diào)用 UserDao,UserDao 負(fù)責(zé)調(diào)用數(shù)據(jù)庫(kù)返回用戶需要的信息。
從這張可以看出 UserController 依賴 UserService、UserService 依賴 UserDao。
如圖 2 所示,假設(shè)在 UserController 中需要使用 UserService,就需要在其 UserController 構(gòu)造函數(shù)中對(duì) UserService 進(jìn)行實(shí)例化。
圖 2:傳統(tǒng)的依賴關(guān)系需要自己管理對(duì)象實(shí)例化
這樣才能 save 方法中使用 UserService,并且調(diào)用其 save 方法。
與傳統(tǒng)的依賴方式不同,Spring IoC 會(huì)通過一個(gè) XML 文件配置對(duì)象之間的關(guān)系。
如圖 3 所示,在 beans 的標(biāo)簽中,定義了兩個(gè) bean,分別是 UserController 和 UserService。在 Class 屬性中定義了 Class 的全程(包含 Namespace)。
圖 3:Spring IoC 的依賴關(guān)系 XML 配置
需要注意的是在 UserController的bean 定義中指定了 contructor-arg 的 ref 為 UserService。
這里的含義是在 UserController 的構(gòu)造函數(shù)中會(huì)引入 UserService,從而說明兩者之間的依賴關(guān)系,也就是 UserController 會(huì)依賴 UserService。
看完了 XML 的配置再回頭看看代碼中有什么改變,如圖 4 所示,在 UserController 的構(gòu)造函數(shù)的初始化參數(shù)中加入 UserService 作為依賴項(xiàng)。
圖 4:Spring IoC 代碼中的改變
不過 New UserService 的動(dòng)作就不再 UserController 中完成了,而是由 Spring 容器完成。
Spring 容器完成 UserService 的初始化之后,在 UserController 需要使用的時(shí)候直接使用這個(gè) UserService 實(shí)體就行了。
圖 5:Spring IoC 的 Spring 容器
這里再將 Spring IoC 做的事情梳理一下,如圖 5 所示:
- 位于中間的 Spring 容器會(huì)讀取 XML 配置文件中的信息,獲取 Bean 之間的依賴關(guān)系。
- Spring 容器通過反射機(jī)制創(chuàng)建對(duì)象的實(shí)例,由于 Spring 容器管理所有注冊(cè) Bean 因此為后續(xù)建立它們之間的依賴關(guān)系打下基礎(chǔ)。
- Spring 容器通過 Bean 之間的依賴關(guān)系創(chuàng)建實(shí)例,同時(shí)保證 Bean 在使用依賴項(xiàng)的時(shí)候直接過去對(duì)應(yīng)的實(shí)例,而不用自己去創(chuàng)建實(shí)例。
說白了 Spring IoC 做的事情就是管理和創(chuàng)建 Bean 的實(shí)例,同時(shí)保證 Bean 之間的依賴關(guān)系。
這里我們引出 Spring IoC,IoC(Inversion of Control)也稱為控制反轉(zhuǎn),也就是對(duì)象定義其依賴關(guān)系的控制反轉(zhuǎn)。
原來這個(gè)過程是:誰(shuí)使用誰(shuí)創(chuàng)建,例如上面的例子中 UserController 需要使用 UserService,于是就由 UserController 創(chuàng)建 UserService 的實(shí)例。
引入 IoC 以后,這個(gè)創(chuàng)建過程發(fā)生的反轉(zhuǎn),這些 UserController 和 UserService 之間的依賴關(guān)系由 XML 文件定義以后由 Spring 容器進(jìn)行創(chuàng)建。
這個(gè)控制權(quán)從對(duì)象的使用者轉(zhuǎn)換為 Spring 容器,就成為控制反轉(zhuǎn)。也就是對(duì)象之間的依賴過程發(fā)生了變化,由原來的主動(dòng)創(chuàng)建,變成了現(xiàn)在被動(dòng)關(guān)聯(lián)(因?yàn)?Spring 容器的參與),這種控制權(quán)顛的現(xiàn)象被稱為控制反轉(zhuǎn)。
Spring IoC 容器
前面說了 IoC 的來歷和概念,實(shí)際上它是用來管理對(duì)象初始化的容器,這里會(huì)針對(duì) Spring IoC 容器介紹其主要功能。
Spring IoC 容器將創(chuàng)建對(duì)象,通過配置設(shè)定它們之間的依賴關(guān)系,并管理它們的生命周期(從創(chuàng)建到銷毀)。
Spring IoC 容器管理的對(duì)象被稱為 Spring Beans,也就是上面例子中提到的 UserController 和 UserService。
通過閱讀配置文件元數(shù)據(jù)提供的指令,容器知道對(duì)哪些對(duì)象進(jìn)行實(shí)例化,配置和組裝。
配這里的置元數(shù)據(jù)就是上面例子的 XML,不過處理 XML 的配置之外還可以通過 Java 注釋或 Java 代碼來表示,大家可以理解為一種配置對(duì)象之間關(guān)系的方式。
說了這么多的 Spring IoC 容器的作用,在 Spring 中實(shí)現(xiàn) IoC 容器的實(shí)際代表者是誰(shuí)呢?
這里介紹兩類 Spring IoC 容器的代表者,分別是:
①Spring BeanFactory 容器
它是最簡(jiǎn)單的容器,用 org.springframework.beans.factory.BeanFactory 接口來定義。
BeanFactory 或者相關(guān)的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的與 Spring 整合的第三方框架的反向兼容性的目的。
②Spring ApplicationContext 容器
添加了更多的企業(yè)特定的功能,例如從一個(gè)屬性文件中解析文本信息的能力,發(fā)布應(yīng)用程序事件給感興趣的事件監(jiān)聽器的能力。
該容器是由 org.springframework.context.ApplicationContext 接口定義。
由于 ApplicationContext 容器包括 BeanFactory 容器的所有功能,同時(shí) BeanFactory 適用于輕量級(jí)應(yīng)用。
這里我們將目光放到 ApplicationContext 容器上,看看它是如何實(shí)現(xiàn) Spring IoC 容器的功能的。
由于 ApplicationContext 是一個(gè)接口,針對(duì)它有幾種不同的實(shí)現(xiàn),這些實(shí)現(xiàn)會(huì)針對(duì)不同使用場(chǎng)景,以下列出三種不同實(shí)現(xiàn):
FileSystemXmlApplicationContext:實(shí)現(xiàn)了從 XML 文件中加載 bean。初始化該類的時(shí)候需要提供 XML 文件的完整路徑。
ClassPathXmlApplicationContext:也實(shí)現(xiàn)了 XML 文件中加載 bean,與上面一種方式不同的是:不需要提供 XML 文件的完整路徑,只需正確配置 CLASSPATH 環(huán)境變量即可,容器會(huì)從 CLASSPATH 中搜索 bean 配置文件。
WebXmlApplicationContext:實(shí)現(xiàn)了在一個(gè) web 應(yīng)用程序的范圍內(nèi)加載在 XML 文件中已被定義的 bean。
由于篇幅原因,這里我們針對(duì) FileSystemXmlApplicationContext 實(shí)現(xiàn) Spring IoC 容器進(jìn)行說明。
圖 6:FileSystemXmlApplicationContext 實(shí)現(xiàn) Spring IoC 容器
如圖 6 所示:
- 在使用 FileSystemXmlApplicationContext 實(shí)現(xiàn)類之前需要引入相關(guān)的包,由于其是接口 ApplicationContext 的實(shí)現(xiàn)類,因此需要引入 ApplicationContext 的包,以及自身 FileSystemXmlApplicationContext 的包。
- 在進(jìn)行 FileSystemXmlApplicationContext 實(shí)例化時(shí)傳入 XML 文件的地址,也就是上文中配置 bean 對(duì)象的 XML 文件地址,這里是“C:/Test/src/Beans.xml”。
- 最后通過 FileSystemXmlApplicationContext 所帶的 getBean 方法,通過傳入 bean id 的方式獲取 bean 對(duì)象的實(shí)例,這里傳入“userController”,從而調(diào)用 userController 中的 save 方法完成業(yè)務(wù)。
Spring IoC 的優(yōu)缺點(diǎn)
在介紹過 Spring IoC 的原理和容器實(shí)現(xiàn)以后,相信大家對(duì) IoC 有所了解了,不過任何技術(shù)和架構(gòu)都有其優(yōu)缺點(diǎn) Spring IoC 也不例外在使用它之前,還需要對(duì)其有清晰的認(rèn)識(shí)。
首先是優(yōu)點(diǎn)的部分:
- 靈活性,由于類之間依賴可以靈活配置,因此可以設(shè)置類對(duì)于接口的依賴,在針對(duì)變現(xiàn)對(duì)應(yīng)的實(shí)現(xiàn)類,這種方式讓依賴接口的實(shí)現(xiàn)類更加方便,提倡面向接口編程,提高程序的可擴(kuò)展性。
- 可讀性,每個(gè)bean之間的依賴關(guān)系清晰,由于 Spring IoC 容器來管理 bean 的實(shí)例,因此不需要?jiǎng)?chuàng)建一堆工廠類來生成不同的 bean。
- 可測(cè)性,由于通過 IoC 的方式讓每個(gè) bean 都解耦了,可以針對(duì)單獨(dú)的 bean 進(jìn)行測(cè)試,而且 bean 之間的依賴關(guān)系也很明確,如果想替換其中的 bean 進(jìn)行測(cè)試也是很容易的事情。
有優(yōu)點(diǎn)就一定有缺點(diǎn):
- 復(fù)雜,由于引入 IoC 容器,對(duì)象生成步驟變得復(fù)雜,本來哪里使用哪里生成對(duì)象的,現(xiàn)在憑空多出 XML 配置依賴項(xiàng)之間的關(guān)系,讓系統(tǒng)調(diào)用變得不太直觀。因此會(huì)增加團(tuán)隊(duì)學(xué)習(xí)成本,需要團(tuán)隊(duì)提升這方面的技能。
- 性能,IoC 容器生成對(duì)象是通過反射方式,在運(yùn)行效率上有一定的損耗,它允程序在運(yùn)行時(shí)(不是編譯時(shí))對(duì)成員進(jìn)行操作。
- 配置,IoC 框架需要進(jìn)行大量的配制工作,無形中會(huì)增加開發(fā)成本。
IoC 與 DI
前面說了 IoC 及控制反轉(zhuǎn),一般來說和 IoC 一同出現(xiàn)的有 DI(Dependency Injection)也就是依賴注入,這兩個(gè)概念之間有什么關(guān)系呢?
在 2004 年 Martin Fowler 在探索 IOC 控制反轉(zhuǎn)問題的時(shí)候,提出:“哪些方面的控制被反轉(zhuǎn)了呢?”,經(jīng)過詳細(xì)地分析和論證后,他得出了答案:“獲得依賴對(duì)象的過程被反轉(zhuǎn)了”。
控制被反轉(zhuǎn)之后,獲得依賴對(duì)象的過程由自身管理變?yōu)榱擞?IOC 容器主動(dòng)注入。
于是,他給“控制反轉(zhuǎn)”取了一個(gè)更合適的名字叫做“依賴注入(Dependency Injection)”。
他的這個(gè)答案,實(shí)際上給出了實(shí)現(xiàn) IOC 的方法:注入。所謂依賴注入,就是由 IoC 容器在運(yùn)行期間,動(dòng)態(tài)地將某種依賴關(guān)系注入到對(duì)象之中。
就好像上面提到的例子一樣,將 UserService 注入到 UserController 一樣,這個(gè)過程是由 Spring IoC 容器來完成的。
因此,依賴注入(DI)和控制反轉(zhuǎn)(IOC)是從不同角度描述同一件事情,就是指通過引入 IOC 容器,利用依賴關(guān)系注入的方式,實(shí)現(xiàn)對(duì)象之間的解耦。
基于對(duì) IoC 和 DI 兩個(gè)概念的理解,再來看看實(shí)現(xiàn) DI 的兩種方式:基于構(gòu)造函數(shù)的 DI 和基于 setter 方法的 DI。
基于構(gòu)造函數(shù)的 DI,在上面的例子中提到過這里進(jìn)行一下回顧,如圖 7 所示,在 XML 的配置文件中定義 UserController 的 bean 同時(shí)將 ref 指定 UserService,也就是需要注入的 bean。
圖 7:構(gòu)造函數(shù) DI 的配置文件
需要注意的是,這里通過設(shè)置 contructor-arg 指定構(gòu)造函數(shù)注入方式。
如圖 8 所示,在類文件中 UserController 的構(gòu)造函數(shù)中傳入的參數(shù)就是 UserService。
Spring IoC 容器在完成依賴注入和對(duì)象初始化以后,在 UserController 中之間使用對(duì)象的實(shí)例展開后續(xù)的業(yè)務(wù)操作。
圖 8:UserController 使用依賴注入和被初始化以后的 UserService 對(duì)象
如圖 9 所示,在配置 bean 節(jié)點(diǎn)中稍微做了調(diào)整,將 contructor-arg 修改為了 property,通過 property 屬性定義與 UserService 的 setter 方法的依賴關(guān)系。
圖 9:UserController 定義 setter 方法的依賴關(guān)系
再來看看類中的修改,如圖 10 所示,與構(gòu)造函數(shù)注入方式不同的是,在 UserController 中加入了一個(gè) setUserService 的方法來設(shè)置 UserService 的屬性,傳入的參數(shù)依舊是 UserService。
圖 10:setter 方法的依賴注入在類中的實(shí)現(xiàn)
DI 的自動(dòng)裝載
上面提到了通過構(gòu)造函數(shù)和 setter 方法來注入備 bean 對(duì)象,其分別使用 XML 配置文件中的
為了減少 XML 配置的數(shù)量,Spring 容器可以在不使用
下面我們來看看幾種自動(dòng)裝配的方式:
①byType,這種方式由屬性數(shù)據(jù)類型自動(dòng)裝配
如果在類中定義了與其他類的依賴關(guān)系,那么 Spring 容器在 XML 配置文件中會(huì)通過類型尋找對(duì)應(yīng)依賴關(guān)系的 bean,然后與之關(guān)聯(lián)。這個(gè)過程容器會(huì)嘗試匹配和連接屬性的類型。
例如 bean A 定義了 X 類型的屬性, Spring 會(huì)在 ApplicationContext 中尋找一個(gè)類型為 X 的 bean,并將其注入 bean A。
如果還是覺得抽象,我們看下面的例子,如圖 11 所示,UserController 設(shè)置 UserService 屬性時(shí)定義了與 UserService 的依賴關(guān)系。
圖 11:定義 UserController 與 UserService 的依賴關(guān)系
如圖 12 所示,在 XML 配置文件中 UserController 就不需要使用 property 屬性定義與 UserService 之間的關(guān)系,取而代之的是使用 autowire=“byType” 的方法。
圖 12:通過 byType 定義關(guān)系
容器通過類中 setUserService 傳入的 UserService 類型自動(dòng)在配置文件中尋找 UserService 對(duì)應(yīng)的類型,從而完成 UserController 和 UserService 依賴關(guān)系,也就是依賴注入,這種方式也是基于類型的自動(dòng)裝載。
②constructor,適用于構(gòu)造函數(shù)參數(shù)類型的自動(dòng)加載
有了 byType 的基礎(chǔ)這個(gè)很好理解,例如 bean A 的構(gòu)造函數(shù)接受 X 類型的參數(shù),容器會(huì)在 XML 尋找 X 類型的 bean,并將其注入到 bean A 的構(gòu)造函數(shù)中。
如圖 13 所示,UserController 在構(gòu)造函數(shù)中定義 UserService 作為初始化參數(shù),確定了 UserController 對(duì) UserService 的依賴。
圖 13:UserController 在構(gòu)造函數(shù)中定義 UserService 作為初始化參數(shù)
如圖 14 所示,在 XML 配置文件中 UserController 只需要設(shè)置 autowire=“constructor”。
告訴容器通過 UserController 類中的構(gòu)造方法將 UserService 注入到 UserController 中,完成 UserController 和 UserService 依賴關(guān)系,這種方式也是基于構(gòu)造器的自動(dòng)裝載。
圖 14:通過 constructor 定義關(guān)系
③byName,通過指定特定的 bean 名稱,容器根據(jù)名稱自動(dòng)選擇 bean 屬性,完成依賴注入
例如:bean A 定義了一個(gè)名為 X 的屬性,容器會(huì)在 XML 尋找一個(gè)名為 X 的 bean,將其注入到 bean A 中。
如圖 15 所示,UserController 中定義了一個(gè)名為 myUserService 的成員屬性,其類型是 UserService。
圖 15:UserController 中定義了一個(gè)名為 myUserService 的成員屬性
如圖 16 所示,在 XML 的配置中 UserController 的 autowire 配置了“byName”。
此時(shí)容器會(huì)根據(jù)類中定義的 myUserService 成員屬性(變量)自動(dòng)關(guān)聯(lián)到 UserService,在 UserController 中 setUserService 時(shí)自動(dòng)裝載 UserService 的實(shí)例。
圖 16:XML 文件中 byName 的定義
總結(jié)
本文從 Spring IoC 的由來說起,通過一個(gè)簡(jiǎn)單的對(duì)象依賴?yán)咏忉屃?Spring IoC 解決的問題。
它將對(duì)象的依賴關(guān)系從對(duì)象內(nèi)部轉(zhuǎn)移到了 IoC 容器中完成,由容器來關(guān)系對(duì)象的注冊(cè)和依賴關(guān)系。
說起 Spring IoC 容器,由 BeanFactory 和 ApplicationContext 接口完成具體工作。
針對(duì)常用的 ApplicationContext 接口的三個(gè)實(shí)現(xiàn)類,分別實(shí)現(xiàn)了根據(jù) XML 加載實(shí)例、根據(jù) CLASSPATH 加載實(shí)例和根據(jù) Web 應(yīng)用程序范圍加載實(shí)例。
在分析完 IoC 的優(yōu)缺點(diǎn)以后,解釋了 IoC 與 DI 之間的關(guān)系,DI 從另外一個(gè)角度解釋了 IoC,它是在 IoC 容器運(yùn)行期間動(dòng)態(tài)地將依賴關(guān)系注入到對(duì)象中。
常見的依賴注入方式有:構(gòu)造函數(shù)注入和 setter 方法注入。同時(shí)也給大家介紹了 DI 的自動(dòng)注入(加載),其內(nèi)容包括 byType、constructor 和 byName 三種。
作者:崔皓
簡(jiǎn)介:十六年開發(fā)和架構(gòu)經(jīng)驗(yàn),曾擔(dān)任過惠普武漢交付中心技術(shù)專家,需求分析師,項(xiàng)目經(jīng)理,后在創(chuàng)業(yè)公司擔(dān)任技術(shù)/產(chǎn)品經(jīng)理。善于學(xué)習(xí),樂于分享。目前專注于技術(shù)架構(gòu)與研發(fā)管理。
編輯:陶家龍
征稿:有投稿、尋求報(bào)道意向技術(shù)人請(qǐng)聯(lián)絡(luò) editor@51cto.com
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】