日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

DI 依赖注入实现原理

發(fā)布時(shí)間:2023/11/27 生活经验 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 DI 依赖注入实现原理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

深度理解依賴(lài)注入(Dependence Injection)

前面的話:提到依賴(lài)注入,大家都會(huì)想到老馬那篇經(jīng)典的文章。其實(shí),本文就是相當(dāng)于對(duì)那篇文章的解讀。所以,如果您對(duì)原文已經(jīng)有了非常深刻的理解,完全不需要再看此文;但是,如果您和筆者一樣,以前曾經(jīng)看過(guò),似乎看懂了,但似乎又沒(méi)抓到什么要領(lǐng),不妨看看筆者這個(gè)解讀,也許對(duì)您理解原文有一定幫助。

1.依賴(lài)在哪里
?? 老馬舉了一個(gè)小例子,是開(kāi)發(fā)一個(gè)電影列舉器(MovieList),這個(gè)電影列舉器需要使用一個(gè)電影查找器(MovieFinder)提供的服務(wù),偽碼如下:

?1/*服務(wù)的接口*/
?2public?interface?MovieFinder?{
?3????ArrayList?findAll();
?4}

?5
?6/*服務(wù)的消費(fèi)者*/
?7class?MovieLister
?8{
?9????public?Movie[]?moviesDirectedBy(String?arg)?{
10????????List?allMovies?=?finder.findAll();
11????????for?(Iterator?it?=?allMovies.iterator();?it.hasNext();)?{
12????????????Movie?movie?=?(Movie)?it.next();
13????????????if?(!movie.getDirector().equals(arg))?it.remove();
14????????}

15????????return?(Movie[])?allMovies.toArray(new?Movie[allMovies.size()]);
16????}

17
18????/*消費(fèi)者內(nèi)部包含一個(gè)將指向具體服務(wù)類(lèi)型的實(shí)體對(duì)象*/
19????private?MovieFinder?finder;
20????/*消費(fèi)者需要在某一個(gè)時(shí)刻去實(shí)例化具體的服務(wù)。這是我們要解耦的關(guān)鍵所在,
21?????*因?yàn)檫@樣的處理方式造成了服務(wù)消費(fèi)者和服務(wù)提供者的強(qiáng)耦合關(guān)系(這種耦合是在編譯期就確定下來(lái)的)。
22?????**/

23????public?MovieLister()?{
24????????finder?=?new?ColonDelimitedMovieFinder("movies1.txt");
25????}

26}

從上面代碼的注釋中可以看到,MovieLister和ColonDelimitedMovieFinder(這可以使任意一個(gè)實(shí)現(xiàn)了MovieFinder接口的類(lèi)型)之間存在強(qiáng)耦合關(guān)系,如下圖所示:
圖1
這使得MovieList很難作為一個(gè)成熟的組件去發(fā)布,因?yàn)樵诓煌膽?yīng)用環(huán)境中(包括同一套軟件系統(tǒng)被不同用戶(hù)使用的時(shí)候),它所要依賴(lài)的電影查找器可能是千差萬(wàn)別的。所以,為了能實(shí)現(xiàn)真正的基于組件的開(kāi)發(fā),必須有一種機(jī)制能同時(shí)滿足下面兩個(gè)要求:
?(1)解除MovieList對(duì)具體MoveFinder類(lèi)型的強(qiáng)依賴(lài)(編譯期依賴(lài))。
?(2)在運(yùn)行的時(shí)候?yàn)镸ovieList提供正確的MovieFinder類(lèi)型的實(shí)例。
???換句話說(shuō),就是在運(yùn)行的時(shí)候才產(chǎn)生MovieList和MovieFinder之間的依賴(lài)關(guān)系(把這種依賴(lài)關(guān)系在一個(gè)合適的時(shí)候“注入”運(yùn)行時(shí)),這恐怕就是Dependency Injection這個(gè)術(shù)語(yǔ)的由來(lái)。再換句話說(shuō),我們提到過(guò)解除強(qiáng)依賴(lài),這并不是說(shuō)MovieList和MovieFinder之間的依賴(lài)關(guān)系不存在了,事實(shí)上MovieList無(wú)論如何也需要某類(lèi)MovieFinder提供的服務(wù),我們只是把這種依賴(lài)的建立時(shí)間推后了,從編譯器推遲到運(yùn)行時(shí)了。
?? 依賴(lài)關(guān)系在OO程序中是廣泛存在的,只要A類(lèi)型中用到了B類(lèi)型實(shí)例,A就依賴(lài)于B。前面筆者談到的內(nèi)容是把概念抽象到了服務(wù)使用者和服務(wù)提供者的角度,這也符合現(xiàn)在SOA的設(shè)計(jì)思路。從另一種抽象方式上來(lái)看,可以把MovieList看成我們要構(gòu)建的主系統(tǒng),而MovieFinder是系統(tǒng)中的plugin,主系統(tǒng)并不強(qiáng)依賴(lài)于任何一個(gè)插件,但一旦插件被加載,主系統(tǒng)就應(yīng)該可以準(zhǔn)確調(diào)用適當(dāng)插件的功能。
?? 其實(shí)不管是面向服務(wù)的編程模式,還是基于插件的框架式編程,為了實(shí)現(xiàn)松耦合(服務(wù)調(diào)用者和提供者之間的or框架和插件之間的),都需要在必要的位置實(shí)現(xiàn)面向接口編程,在此基礎(chǔ)之上,還應(yīng)該有一種方便的機(jī)制實(shí)現(xiàn)具體類(lèi)型之間的運(yùn)行時(shí)綁定,這就是DI所要解決的問(wèn)題。

2.DI的實(shí)現(xiàn)方式
?? 和上面的圖1對(duì)應(yīng)的是,如果我們的系統(tǒng)實(shí)現(xiàn)了依賴(lài)注入,組件間的依賴(lài)關(guān)系就變成了圖2:
圖2
說(shuō)白了,就是要提供一個(gè)容器,由容器來(lái)完成(1)具體ServiceProvider的創(chuàng)建(2)ServiceUser和ServiceProvider的運(yùn)行時(shí)綁定。下面我們就依次來(lái)看一下三種典型的依賴(lài)注入方式的實(shí)現(xiàn)。特別要說(shuō)明的是,要理解依賴(lài)注入的機(jī)制,關(guān)鍵是理解容器的實(shí)現(xiàn)方式。本文后面給出的容器參考實(shí)現(xiàn),均為黃忠成老師的代碼,筆者僅在其中加上了一些關(guān)鍵注釋而已。

2.1 Constructor Injection(構(gòu)造器注入)
?我們可以看到,在整個(gè)依賴(lài)注入的數(shù)據(jù)結(jié)構(gòu)中,涉及到的重要的類(lèi)型就是ServiceUser, ServiceProvider和Assembler三者,而這里所說(shuō)的構(gòu)造器,指的是ServiceUser的構(gòu)造器。也就是說(shuō),在構(gòu)造ServiceUser實(shí)例的時(shí)候,才把真正的ServiceProvider傳給他:
1class?MovieLister
2{
3???//其他內(nèi)容,省略
4
5???public?MovieLister(MovieFinder?finder)
6???{
7???????this.finder?=?finder;
8???}

9}
接下來(lái)我們看看Assembler應(yīng)該如何構(gòu)建:
?1private?MutablePicoContainer?configureContainer()?{
?2????MutablePicoContainer?pico?=?new?DefaultPicoContainer();
?3????
?4????//下面就是把ServiceProvider和ServiceUser都放入容器的過(guò)程,以后就由容器來(lái)提供ServiceUser的已完成依賴(lài)注入實(shí)例,
?5????//其中用到的實(shí)例參數(shù)和類(lèi)型參數(shù)一般是從配置檔中讀取的,這里是個(gè)簡(jiǎn)單的寫(xiě)法。
?6????//所有的依賴(lài)注入方法都會(huì)有類(lèi)似的容器初始化過(guò)程,本文在后面的小節(jié)中就不再重復(fù)這一段代碼了。
?7????Parameter[]?finderParams?=??{new?ConstantParameter("movies1.txt")};
?8????pico.registerComponentImplementation(MovieFinder.class,?ColonMovieFinder.class,?finderParams);
?9????pico.registerComponentImplementation(MovieLister.class);
10????//至此,容器里面裝入了兩個(gè)類(lèi)型,其中沒(méi)給出構(gòu)造參數(shù)的那一個(gè)(MovieLister)將依靠其在構(gòu)造器中定義的傳入?yún)?shù)類(lèi)型,在容器中
11????//進(jìn)行查找,找到一個(gè)類(lèi)型匹配項(xiàng)即可進(jìn)行構(gòu)造初始化。
12????return?pico;
13}
需要在強(qiáng)調(diào)一下的是,依賴(lài)并未消失,只是延后到了容器被構(gòu)建的時(shí)刻。所以正如圖2中您已經(jīng)看到的,容器本身(更準(zhǔn)確的說(shuō),是一個(gè)容器運(yùn)行實(shí)例的構(gòu)建過(guò)程)對(duì)ServiceUser和ServiceProvoder都是存在依賴(lài)關(guān)系的。所以,在這樣的體系結(jié)構(gòu)里,ServiceUser、ServiceProvider和容器都是穩(wěn)定的,互相之間也沒(méi)有任何依賴(lài)關(guān)系;所有的依賴(lài)關(guān)系、所有的變化都被封裝進(jìn)了容器實(shí)例的創(chuàng)建過(guò)程里,符合我們對(duì)服務(wù)應(yīng)用的理解。而且,在實(shí)際開(kāi)發(fā)中我們一般會(huì)采用配置文件來(lái)輔助容器實(shí)例的創(chuàng)建,將這種變化性排斥到編譯期之外。
?? 即使還沒(méi)給出后面的代碼,你也一定猜得到,這個(gè)container類(lèi)一定有一個(gè)GetInstance(Type t)這樣的方法,這個(gè)方法會(huì)為我們返回一個(gè)已經(jīng)注入完畢的MovieLister。 一個(gè)簡(jiǎn)單的應(yīng)用如下:
1public?void?testWithPico()?
2{
3????MutablePicoContainer?pico?=?configureContainer();
4????MovieLister?lister?=?(MovieLister)?pico.getComponentInstance(MovieLister.class);
5????Movie[]?movies?=?lister.moviesDirectedBy("Sergio?Leone");
6????assertEquals("Once?Upon?a?Time?in?the?West",?movies[0].getTitle());
7}
上面最關(guān)鍵的就是對(duì)pico.getComponentInstance的調(diào)用。Assembler會(huì)在這個(gè)時(shí)候調(diào)用MovieLister的構(gòu)造器,構(gòu)造器的參數(shù)就是當(dāng)時(shí)通過(guò)pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams)設(shè)置進(jìn)去的實(shí)際的ServiceProvider--ColonMovieFinder。下面請(qǐng)看這個(gè)容器的參考代碼:
構(gòu)造注入所需容器的偽碼

2.2 Setter Injection(設(shè)值注入)
?? 這種注入方式和構(gòu)造注入實(shí)在很類(lèi)似,唯一的區(qū)別就是前者在構(gòu)造函數(shù)的調(diào)用過(guò)程中進(jìn)行注入,而它是通過(guò)給屬性賦值來(lái)進(jìn)行注入。無(wú)怪乎PicoContainer和Spring都是同時(shí)支持這兩種注入方式。Spring對(duì)通過(guò)XML進(jìn)行配置有比較好的支持,也使得Spring中更常使用設(shè)值注入的方式:

?1<beans>
?2????<bean?id="MovieLister"?class="spring.MovieLister">
?3????????<property?name="finder">
?4????????????<ref?local="MovieFinder"/>
?5????????</property>
?6????</bean>
?7????<bean?id="MovieFinder"?class="spring.ColonMovieFinder">
?8????????<property?name="filename">
?9????????????<value>movies1.txt</value>
10????????</property>
11????</bean>
12</beans>

下面也給出支持設(shè)值注入的容器參考實(shí)現(xiàn),大家可以和構(gòu)造器注入的容器對(duì)照起來(lái)看,里面的差別很小,主要的差別就在于,在獲取對(duì)象實(shí)例(GetInstance)的時(shí)候,前者是通過(guò)反射得到待創(chuàng)建類(lèi)型的構(gòu)造器信息,然后根據(jù)構(gòu)造器傳入?yún)?shù)的類(lèi)型在容器中進(jìn)行查找,并構(gòu)造出合適的實(shí)例;而后者是通過(guò)反射得到待創(chuàng)建類(lèi)型的所有屬性,然后根據(jù)屬性的類(lèi)型在容器中查找相應(yīng)類(lèi)型的實(shí)例。

設(shè)值注入的容器實(shí)現(xiàn)偽碼

2.3 Interface Injection (接口注入)
?? 這是筆者認(rèn)為最不夠優(yōu)雅的一種依賴(lài)注入方式。要實(shí)現(xiàn)接口注入,首先ServiceProvider要給出一個(gè)接口定義:

1public?interface?InjectFinder?{
2????void?injectFinder(MovieFinder?finder);
3}

接下來(lái),ServiceUser必須實(shí)現(xiàn)這個(gè)接口:

1class?MovieLister:?InjectFinder
2{
3???public?void?injectFinder(MovieFinder?finder)?{
4??????this.finder?=?finder;
5????}

6}

容器所要做的,就是根據(jù)接口定義調(diào)用其中的inject方法完成注入過(guò)程,這里就不在贅述了,總的原理和上面兩種依賴(lài)注入模式?jīng)]有太多區(qū)別。

2.4? 除了DI,還有Service Locator
?? 上面提到的依賴(lài)注入只是消除ServiceUser和ServiceProvider之間的依賴(lài)關(guān)系的一種方法,還有另一種方法:服務(wù)定位器(Service Locator)。也就是說(shuō),由ServiceLocator來(lái)專(zhuān)門(mén)負(fù)責(zé)提供具體的ServiceProvider。當(dāng)然,這樣的話ServiceUser不僅要依賴(lài)于服務(wù)的接口,還依賴(lài)于ServiceContract。仍然是最早提到過(guò)的電影列舉器的例子,如果使用Service Locator來(lái)解除依賴(lài)的話,整個(gè)依賴(lài)關(guān)系應(yīng)當(dāng)如下圖所示:
圖3
用起來(lái)也很簡(jiǎn)單,在一個(gè)適當(dāng)?shù)奈恢?#xff08;比如在一組相關(guān)服務(wù)即將被調(diào)用之前)對(duì)ServiceLocator進(jìn)行初始化,用到的時(shí)候就直接用ServiceLocator返回ServiceProvider實(shí)例:

1//服務(wù)定位器的初始化
2ServiceLocator?locator?=?new?ServiceLocator();
3locator.loadService("MovieFinder",?new?ColonMovieFinder("movies1.txt"));
4ServiceLocator.load(locator);

5//服務(wù)定義器的使用
6//其實(shí)這個(gè)使用方式體現(xiàn)了服務(wù)定位器和依賴(lài)注入模式的最大差別:ServiceUser需要顯示的調(diào)用ServiceLocator,從而獲取自己需要的服務(wù)對(duì)象;
7//而依賴(lài)注入則是隱式的由容器完成了這一切。
8MovieFinder?finder?=?(MovieFinder)?ServiceLocator.getService("MovieFinder");
9

正因?yàn)樯厦嫣岬竭^(guò)的ServiceUser對(duì)ServiceLocator的依賴(lài)性,從提高模塊的獨(dú)立性(比如說(shuō),你可能把你構(gòu)造的ServiceUser或者ServiceProvider給第三方使用)上來(lái)說(shuō),依賴(lài)注入可能更好一些,這恐怕也是為什么大多數(shù)的IOC框架都選用了DI的原因。ServiceLocator最大的優(yōu)點(diǎn)可能在于實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單,如果您開(kāi)發(fā)的應(yīng)用沒(méi)有復(fù)雜到需要采用一個(gè)IOC框架的程度,也許您可以試著采用它。

3.廣義的服務(wù)
?? 文中很多地方提到服務(wù)使用者(ServiceUser)和服務(wù)提供者(ServiceProvider)的概念,這里的“服務(wù)”是一種非常廣義的概念,在語(yǔ)法層面就是指最普通的依賴(lài)關(guān)系(類(lèi)型A中有一個(gè)B類(lèi)型的變量,則A依賴(lài)于B)。如果您把服務(wù)理解為WCF或者Web Service中的那種服務(wù)概念,您會(huì)發(fā)現(xiàn)上面所說(shuō)的所有技術(shù)手段都是沒(méi)有意義的。以WCF而論,其客戶(hù)端和服務(wù)器端本就是依賴(lài)于Contract的松耦合關(guān)系,其實(shí)這也從另一個(gè)角度說(shuō)明了SOA應(yīng)用的優(yōu)勢(shì)所在。

總結(jié)

以上是生活随笔為你收集整理的DI 依赖注入实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。