相信全部面試java開發的童鞋必定都被問到過是否使用過Spring,是否瞭解其IOC容器,爲何不直接使用工廠模式,以及究竟IOC和DI區別在於哪裏這種問題。今天就結合JAVA語言,解釋一下到底是如何衍生出DI模式,以及其在Spring中的實現。html
初學Java,咱們必定會學到面向對象的編程思想,以及使用new關鍵字新建一個對象。假設如今有一個郵件發送系統,該系統包含拼寫檢查功能。那麼本着面向對象的思想以及關注點分離的思想,咱們會將其分解爲兩個類:Emailer
和SpellChecker
。其中,Emailer
依賴着SpellChecker
提供的服務,這兩個類的實現以下:前端
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(){ spellChecker = new SpellChecker(); } }
能夠看到咱們在構造器中使用new新建了一個SpellChecker的對象。java
如今咱們來分析一下這個實現的不足之處:面試
可測試性
:假設如今我但願測試Emailer的功能是否完善,可是此時SpellChecker並無完成開發與測試,那麼咱們將沒法對Emailer進行測試。就算SpellChecker已經開發完成,可是咱們也沒法排除當前的錯誤是否和SpellChecker的實現無關。可維護性
:假設如今支持多語種,那麼我須要分別實現一個EnglishEmailer和FrenchEmailer類。他們的構造函數中分別初始化EnglishSpellChecker和FrenchSpellChecker。之後每增長一個語種都須要新建一個新的Emailer類。而這些類的代碼本質上都是重複的。更不要提假設裏面所以咱們就須要一種新的初始化依賴的方式。編程
既然在調用依賴的類中初始化依賴這麼麻煩,不如將構建完成的依賴傳入調用的類。服務器
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實現,如Emailer e = new Emailer(new MockSpellChecker())
來對Emailer進行測試。那麼這種實現的缺點在哪裏呢?微信
首先,調用Emailer的代碼須要知道如何去初始化SpellChecker,而這明顯暴露了Emailer的內部實現,違背了信息隱藏的思想。其次,一旦依賴發生變化,好比Emailer還須要依賴一個定時裝置Scheduler
來實現定時發送郵件,那麼全部的調用Emailer的代碼都須要發生改變。顯然,這種寫法的可維護性依然不高。框架
那麼,咱們是否能夠將全部對象構建的代碼提取出來,像工廠標準件同樣生產出來。全部對對象的調用都經過工廠提供。函數
public class SpellChecker{ ... public void check(){ ... } } public class Emailer{ private SpellChecker spellChecker; public Emailer(SpellChecker spellChecker){ this.spellChecker = spellChecker; } } //上面這部分代碼不變,仍是經過在構造器中傳入依賴的方式初始化依賴 public class EmailerFactory { public Emailer newFrenchEmailer(){ return new Emailer(new FrenchSpellChecker()); } } //調用 Emailer email = new EmailerFactory().newFrenchEmailer();
這裏,調用方無需瞭解內部對SpellChecker的依賴。不管以後Emailer的依賴發生什麼樣的變化,客戶端代碼都不會受到影響。那麼這種設計有沒有缺陷呢?測試
固然是有的。Emailer的測試和以前同樣,咱們能夠經過傳入Mock的對象來對其進行測試。那麼調用Emailer的服務怎麼辦呀?在調用方看來咱們只是依賴着Factory對象,所以咱們須要經過定義Factory返回一個Mock對象才行,同時這個對象還不能影響真正的Factory的實現。
除此之外,每當咱們對一個新的語種添加支持時,咱們都必須添加一段新的代碼,以下:
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; } }
而這兩段初始化代碼基本上是徹底相同的!而假設之後咱們須要實現一個全球通用版本。。。
光是無聊的工廠模式代碼就要花費咱們大量的時間!
有沒有這樣一個東西,客戶端代碼報出它的編號key,它就會返回那個對象的實例。固然這個實例是根據配置生成的。好比Emailer English
這樣的key,就會返回英語的Emailer。這種思路衍生出了服務定位模式。這個模式至關於站在了全部工廠模式的最前端。它就像是一個老式的電話中轉服務,調用服務的人輸入服務的惟一編號,即電話號碼,而服務定位器找到該服務並返回該服務的實例。調用以下:
Emailer emailer = (Emailer) new ServiceLocator().get("Emailer");
JNDI(Java Naming and Directory Interface)
就是該思想下的一個實現。服務的提供方在JNDI上註冊服務,以後調用方在JNDI上檢索服務,實現兩者之間的解耦。
這個模式的問題和工廠模式相似,難以測試以及須要管理共享狀態。其次,經過使用String類型的Key來獲取服務沒法在編譯時對服務調用是否正確以及服務類型是否正確進行檢查。
這裏將不會給出JNDI的具體實現,對JNDI的概念有困惑的能夠查看這篇文章
看來,任何和構造對象相關的代碼夾雜在業務代碼中都會帶來麻煩,那麼咱們能夠將這部分代碼全權委託給構造框架,業務代碼經過依賴注入從而關注於業務自己,而框架能夠經過配置甚至是自動的生成對象注入到客戶端。從而實現兩者的徹底解耦。
至此,對象關聯圖的構造,聯繫和組裝將和業務代碼徹底無關,這種狀況也被成爲控制反轉(IOC)
不一樣的框架對於依賴注入的實現是不一樣的,可是本質上來講,他們都確保了客戶端無需在業務代碼中瞭解注入的依賴是如何初始化的。
那麼IOC和DI之間的區別到底是什麼呢?
IOC這個概念所表示的領域其實超出了依賴注入的範圍,它更多強調的是控制反轉,也就是說,這個對象是別人替你建立好的。所以DI是IOC的一種實現機制。而控制反轉能夠運用於更多的場景,如:
IOC不只負責建立對象,還須要管理對象的生命週期。不一樣的生命週期須要觸發不一樣的調用,這些調用被稱爲回調函數。除此之外,IOC容器管理的對象須要被打上標記,好比使用@Autowire
,@Component
註解的類和對象,以及繼承了Servlet
接口的Servlet纔會被Servlet容器管理。
所以咱們常見的Spring更像是將IOC和DI思想結合在一塊兒生成的產物。
更多關於IOC VS DI能夠參考這篇文章
Spring是一個輕量級的依賴注入框架,它已經成了全部JAVA開發者沒法躲開的開發大禮包。Spring提供了三種依賴注入的方式:XML,註解和Java Config
XML方式曾經很是流行,可是這種方式也逐漸暴露出問題,主要的問題在於沒法對注入的依賴進行類型檢查,從而致使代碼沒法在編譯期間識別出問題,只能在運行期間拋出異常。如今主要推薦自動掃描並注入以及經過JavaConfig代碼來配置。而XML配置通常用於Legacy System上
自動掃描並注入的代碼以下:
public class Emailer{ @Autowired private SpellChecker spellChecker; public class Emailer(SpellChecker spellChecker){ } } @Component public class SpellChecker{ ... } @ComponentScan public class EmailerConfig{ }
這裏只給出直接在依賴對象上添加註解的形式,還能夠經過構造器和setter注入依賴,這裏就很少說了。
Java Config則是將配置代碼單獨提取出來:
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(){ return new Emailer(new EnglishSpellChecker()); } }
固然,這裏也能夠經過依賴注入的方式來確保傳入的對象是單例的(默認狀況下Spring生成的對象爲單例)
@Configuration public class EmailerConfig{ @Bean public Emailer EnglishEmailer(SpellChecker spellChecker){ return new Emailer(spellChecker); } }
想要了解更多開發技術,面試教程以及互聯網公司內推,歡迎關注個人微信公衆號!將會不按期的發放福利哦~