摘要:上面這部分代碼不變,還是通過在構(gòu)造器中傳入依賴的方式初始化依賴調(diào)用這里,調(diào)用方無需了解內(nèi)部對的依賴。而配置一般用于上自動掃描并注入的代碼如下這里只給出直接在依賴對象上添加注解的形式,還可以通過構(gòu)造器和注入依賴,這里就不多說了。
前言
相信所有面試java開發(fā)的童鞋一定都被問到過是否使用過Spring,是否了解其IOC容器,為什么不直接使用工廠模式,以及究竟IOC和DI區(qū)別在于哪里這種問題。今天就結(jié)合JAVA語言,解釋一下究竟是如何衍生出DI模式,以及其在Spring中的實現(xiàn)。
很久很久以前初學(xué)Java,我們一定會學(xué)到面向?qū)ο蟮木幊趟枷耄约笆褂胣ew關(guān)鍵字新建一個對象。假設(shè)現(xiàn)在有一個郵件發(fā)送系統(tǒng),該系統(tǒng)包含拼寫檢查功能。那么本著面向?qū)ο蟮乃枷胍约瓣P(guān)注點分離的思想,我們會將其分解為兩個類:Emailer和SpellChecker。其中,Emailer依賴著SpellChecker提供的服務(wù),這兩個類的實現(xiàn)如下:
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(){ spellChecker = new SpellChecker(); } }
可以看到我們在構(gòu)造器中使用new新建了一個SpellChecker的對象。
現(xiàn)在我們來分析一下這個實現(xiàn)的不足之處:
可測試性:假設(shè)現(xiàn)在我希望測試Emailer的功能是否完善,但是此時SpellChecker并沒有完成開發(fā)與測試,那么我們將無法對Emailer進(jìn)行測試。就算SpellChecker已經(jīng)開發(fā)完成,但是我們也無法排除當(dāng)前的錯誤是否和SpellChecker的實現(xiàn)無關(guān)。
可維護(hù)性:假設(shè)現(xiàn)在支持多語種,那么我需要分別實現(xiàn)一個EnglishEmailer和FrenchEmailer類。他們的構(gòu)造函數(shù)中分別初始化EnglishSpellChecker和FrenchSpellChecker。以后每增加一個語種都需要新建一個新的Emailer類。而這些類的代碼本質(zhì)上都是重復(fù)的。更不要提假設(shè)里面
因此我們就需要一種新的初始化依賴的方式。
自己初始化不行,那你給我一個現(xiàn)成的吧!既然在調(diào)用依賴的類中初始化依賴這么麻煩,不如將構(gòu)建完成的依賴傳入調(diào)用的類。
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(SpellChecker spellChecker){ this.spellChecker = spellChecker; } } //使用 Emailer email = new Emailer(new EnglishSpellChecker())
從測試性的角度來說,這個代碼明顯更加易于測試了,我們可以提供SpellChecker的一個Mock實現(xiàn),如Emailer e = new Emailer(new MockSpellChecker())來對Emailer進(jìn)行測試。那么這種實現(xiàn)的缺點在哪里呢?
首先,調(diào)用Emailer的代碼需要知道如何去初始化SpellChecker,而這明顯暴露了Emailer的內(nèi)部實現(xiàn),違背了信息隱藏的思想。其次,一旦依賴發(fā)生變化,比如Emailer還需要依賴一個定時裝置Scheduler來實現(xiàn)定時發(fā)送郵件,那么所有的調(diào)用Emailer的代碼都需要發(fā)生改變。顯然,這種寫法的可維護(hù)性依然不高。
工廠模式閃亮登場,所有的初始化都交給我了!那么,我們是否可以將所有對象構(gòu)建的代碼提取出來,像工廠標(biāo)準(zhǔn)件一樣生產(chǎn)出來。所有對對象的調(diào)用都通過工廠提供。
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(SpellChecker spellChecker){ this.spellChecker = spellChecker; } } //上面這部分代碼不變,還是通過在構(gòu)造器中傳入依賴的方式初始化依賴 public class EmailerFactory { public Emailer newFrenchEmailer(){ return new Emailer(new FrenchSpellChecker()); } } //調(diào)用 Emailer email = new EmailerFactory().newFrenchEmailer();
這里,調(diào)用方無需了解內(nèi)部對SpellChecker的依賴。無論之后Emailer的依賴發(fā)生什么樣的變化,客戶端代碼都不會受到影響。那么這種設(shè)計有沒有缺陷呢?
當(dāng)然是有的。Emailer的測試和之前一樣,我們可以通過傳入Mock的對象來對其進(jìn)行測試。那么調(diào)用Emailer的服務(wù)怎么辦呀?在調(diào)用方看來我們只是依賴著Factory對象,因此我們需要通過定義Factory返回一個Mock對象才行,同時這個對象還不能影響真正的Factory的實現(xiàn)。
除此以外,每當(dāng)我們對一個新的語種添加支持時,我們都必須添加一段新的代碼,如下:
public class EmailerFactory { public Emailer newJapaneseEmailer() { Emailer service = new Emailer(); service.setSpellChecker(new JapaneseSpellChecker()); return service; } public Emailer newFrenchEmailer() { Emailer service = new Emailer(); service.setSpellChecker(new FrenchSpellChecker()); return service; } }
而這兩段初始化代碼基本上是完全相同的!而假設(shè)以后我們需要實現(xiàn)一個全球通用版本。。。
光是無聊的工廠模式代碼就要花費我們大量的時間!
有沒有這樣一個東西,客戶端代碼報出它的編號key,它就會返回那個對象的實例。當(dāng)然這個實例是根據(jù)配置生成的。比如Emailer English這樣的key,就會返回英語的Emailer。這種思路衍生出了服務(wù)定位模式。這個模式相當(dāng)于站在了所有工廠模式的最前端。它就像是一個老式的電話中轉(zhuǎn)服務(wù),調(diào)用服務(wù)的人輸入服務(wù)的唯一編號,即電話號碼,而服務(wù)定位器找到該服務(wù)并返回該服務(wù)的實例。調(diào)用如下:
Emailer emailer = (Emailer) new ServiceLocator().get("Emailer");
JNDI(Java Naming and Directory Interface)就是該思想下的一個實現(xiàn)。服務(wù)的提供方在JNDI上注冊服務(wù),之后調(diào)用方在JNDI上檢索服務(wù),實現(xiàn)二者之間的解耦。
這個模式的問題和工廠模式類似,難以測試以及需要管理共享狀態(tài)。其次,通過使用String類型的Key來獲取服務(wù)無法在編譯時對服務(wù)調(diào)用是否正確以及服務(wù)類型是否正確進(jìn)行檢查。
這里將不會給出JNDI的具體實現(xiàn),對JNDI的概念有困惑的可以查看這篇文章
Injector隆重登場看來,任何和構(gòu)造對象相關(guān)的代碼夾雜在業(yè)務(wù)代碼中都會帶來麻煩,那么我們可以將這部分代碼全權(quán)委托給構(gòu)造框架,業(yè)務(wù)代碼通過依賴注入從而關(guān)注于業(yè)務(wù)本身,而框架可以通過配置甚至是自動的生成對象注入到客戶端。從而實現(xiàn)二者的完全解耦。
至此,對象關(guān)聯(lián)圖的構(gòu)造,聯(lián)系和組裝將和業(yè)務(wù)代碼完全無關(guān),這種情況也被成為控制反轉(zhuǎn)(IOC)
不同的框架對于依賴注入的實現(xiàn)是不同的,但是本質(zhì)上來說,他們都確保了客戶端無需在業(yè)務(wù)代碼中了解注入的依賴是如何初始化的。
IOC vs DI那么IOC和DI之間的區(qū)別究竟是什么呢?
IOC這個概念所表示的領(lǐng)域其實超出了依賴注入的范圍,它更多強調(diào)的是控制反轉(zhuǎn),也就是說,這個對象是別人替你創(chuàng)建好的。因此DI是IOC的一種實現(xiàn)機制。而控制反轉(zhuǎn)可以運用于更多的場景,如:
J2EE應(yīng)用服務(wù)器中的一個模塊,比如Servlet
框架自動調(diào)用的測試方法
點擊鼠標(biāo)后調(diào)用的事件處理器
IOC不僅負(fù)責(zé)創(chuàng)建對象,還需要管理對象的生命周期。不同的生命周期需要觸發(fā)不同的調(diào)用,這些調(diào)用被稱為回調(diào)函數(shù)。除此以外,IOC容器管理的對象需要被打上標(biāo)記,比如使用@Autowire,@Component注解的類和對象,以及繼承了Servlet接口的Servlet才會被Servlet容器管理。
因此我們常見的Spring更像是將IOC和DI思想結(jié)合在一起生成的產(chǎn)物。
更多關(guān)于IOC VS DI可以參考這篇文章
SpringSpring是一個輕量級的依賴注入框架,它已經(jīng)成了所有JAVA開發(fā)者無法躲開的開發(fā)大禮包。Spring提供了三種依賴注入的方式:XML,注解和Java Config
XML方式曾經(jīng)非常流行,但是這種方式也逐漸暴露出問題,主要的問題在于無法對注入的依賴進(jìn)行類型檢查,從而導(dǎo)致代碼無法在編譯期間識別出問題,只能在運行期間拋出異?!,F(xiàn)在主要推薦自動掃描并注入以及通過JavaConfig代碼來配置。而XML配置一般用于Legacy System上
Autowired自動掃描并注入的代碼如下:
public class Emailer{ @Autowired private SpellChecker spellChecker; public class Emailer(SpellChecker spellChecker){ } } @Component public class SpellChecker{ ... } @ComponentScan public class EmailerConfig{ }
這里只給出直接在依賴對象上添加注解的形式,還可以通過構(gòu)造器和setter注入依賴,這里就不多說了。
Java ConfigJava Config則是將配置代碼多帶帶提取出來:
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(){ return new Emailer(new EnglishSpellChecker()); } }
當(dāng)然,這里也可以通過依賴注入的方式來確保傳入的對象是單例的(默認(rèn)情況下Spring生成的對象為單例)
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(SpellChecker spellChecker){ return new Emailer(spellChecker); } }
想要了解更多開發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號!將會不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/69658.html
摘要:引述最近看設(shè)計模式以及代碼,對于控制反轉(zhuǎn)以及依賴注入這些概念非常困惑,于是找了一些資料,以下是對于控制反轉(zhuǎn)的一下理解。其中最常見的方式叫做依賴注入,簡稱,還有一種方式叫依賴查找。在軟件工程中,依賴注入是種實現(xiàn)控制反轉(zhuǎn)用于解決依賴性設(shè)計模式。 引述 最近看設(shè)計模式以及l(fā)aravel代碼,對于控制反轉(zhuǎn)以及依賴注入這些概念非常困惑,于是找了一些資料,以下是對于控制反轉(zhuǎn)的一下理解。 概念 Io...
摘要:劃下重點,服務(wù)容器是用于管理類的依賴和執(zhí)行依賴注入的工具。類的實例化及其依賴的注入,完全由服務(wù)容器自動的去完成。 本文首發(fā)于 深入剖析 Laravel 服務(wù)容器,轉(zhuǎn)載請注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。 之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請求,又是如何生成響應(yīng)并最終呈現(xiàn)給用戶的工作原理。 本章將帶領(lǐng)大...
摘要:然而,我們需要注意的是僅是軟件設(shè)計模式依賴注入的一種便利的實現(xiàn)形式。容器本身不是依賴注入的必要條件,在框架他只是讓其變得更加簡便。首先,讓我們探索下為什么依賴注入是有益的。繼續(xù)深入讓我們通過另一個示例來加深對依賴注入的理解。 聲明:本文并非博主原創(chuàng),而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當(dāng)然也不是原汁原味的翻譯,能保證9...
摘要:本文一大半內(nèi)容都是通過舉例來讓讀者去理解什么是控制反轉(zhuǎn)和依賴注入,通過理解這些概念,來更加深入。這種由外部負(fù)責(zé)其依賴需求的行為,我們可以稱其為控制反轉(zhuǎn)。工廠模式,依賴轉(zhuǎn)移當(dāng)然,實現(xiàn)控制反轉(zhuǎn)的方法有幾種。 容器,字面上理解就是裝東西的東西。常見的變量、對象屬性等都可以算是容器。一個容器能夠裝什么,全部取決于你對該容器的定義。當(dāng)然,有這樣一種容器,它存放的不是文本、數(shù)值,而是對象、對象的描...
摘要:控制反轉(zhuǎn)容器控制反轉(zhuǎn)使依賴注入變得更加便捷。有瑕疵控制反轉(zhuǎn)容器是實現(xiàn)的控制翻轉(zhuǎn)容器的一種替代方案。容器的獨立使用即使沒有使用框架,我們?nèi)匀豢梢栽陧椖恐惺褂冒惭b組件來使用的控制反轉(zhuǎn)容器。在沒有給定任何信息的情況下,容器是無法實例化相關(guān)依賴的。 聲明:本文并非博主原創(chuàng),而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當(dāng)然也不是原汁原味...
閱讀 783·2021-11-23 09:51
閱讀 848·2021-11-23 09:51
閱讀 2517·2021-11-15 18:01
閱讀 3875·2021-10-11 11:07
閱讀 2414·2021-09-22 15:30
閱讀 1084·2021-09-22 14:59
閱讀 1567·2019-08-30 15:55
閱讀 1763·2019-08-30 15:52