看完此文,別說你不懂Java IoC是什么!
baoXing
讀完需要
速讀僅需 10 分鐘
前言
很多小伙伴會對Spring的IoC有些疑惑,什么是控制反轉(zhuǎn)?
我為何要使用IoC 把控制權(quán)交給容器這樣對我有什么好處?
書上只講理論, 我現(xiàn)在都不能體會Spring的IoC用與不用相比有什么好處,
由Spring托管有什么好處呢?我現(xiàn)在感覺用Spring的set注入就是看起來代碼NB點,
完全不理解到底有什么優(yōu)勢啊……
基于如上讓你徹底搞定理解IoC,從此不再困惑
廢話不多說(正文依舊有),我們開始吧!
原生Servlet
我們先從原生Servlet時代的三層架構(gòu)(MVC)開始切入
創(chuàng)建maven工程 引入依賴
?<dependency>
????<groupId>javax.servletgroupId>
????<artifactId>javax.servlet-apiartifactId>
????<version>3.1.0version>
????<scope>providedscope>
dependency>
創(chuàng)建測試
@WebServlet(urlPatterns?=?"/poXing")
public?class?PoXingServlet?extends?HttpServlet?{
????@Override
????protected?void?doGet(HttpServletRequest?request,?HttpServletResponse?response)
????????????throws?ServletException,?IOException?{
????????response.getWriter().println("PoXingServlet?run?......");
????}
}
瀏覽器訪問
訪問路徑
http://localhost:8080/spring-framework-ioc-learning/poXing
(http://ip:端口號/[context-path(默認是工程名稱可以自行修改)]/uri)
瀏覽器可輸出 PoXingServlet run ...... ?此時已完成基礎(chǔ)Servlet
進一步優(yōu)化 添加 Service 和 Dao
這里為演示簡單 并未引入輸入庫相關(guān)依賴 故此處 Dao 只是模擬有數(shù)據(jù)庫的存在, 不深入
工程目錄創(chuàng)建以下類與接口

三層架構(gòu)中組件與依賴應(yīng)如下圖所示

添加Dao
public?interface?IDemoDao?{
????List?findAll() ;
}
public?class?DemoDaoImpl?implements?IDemoDao?{
????@Override
????public?List?findAll()? {
????????//?此處為演示方便不連接數(shù)據(jù)庫?模擬返回數(shù)據(jù)庫list結(jié)果
????????return?Arrays.asList("aaa",?"bbb",?"ccc");
????}
}
添加Service
public?interface?IDemoService?{
????List?findAll() ;
}
public?class?DemoServiceImpl?implements?IDemoService?{
????private?IDemoDao?demoDao?=?new?DemoDaoImpl();
????@Override
????public?List?findAll()? {
????????return?demoDao.findAll();
????}
}
修改DemoServlet
將我們前面創(chuàng)建的 PoXingServlet 內(nèi)容拷貝 并加以修改
@WebServlet(urlPatterns?=?"/demoServlet")
public?class?DemoServlet?extends?HttpServlet?{
????IDemoService?demoService?=?new?DemoServiceImpl();
????@Override
????protected?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp)
????????????throws?ServletException,?IOException?{
????????resp.getWriter().println(demoService.findAll().toString());
????}
}
運行測試
訪問路徑
http://localhost:8080/spring-framework-ioc-learning/demoServlet
(http://ip:端口號/[context-path(默認是工程名稱可以自行修改)]/uri)
瀏覽器可輸出 ['aaa', 'bbb', 'ccc'] ?此時已完成基礎(chǔ)Servlet
基礎(chǔ)搭建工作完成,下面才是我們的重點! 我們的重點!! 我們的重點!!!
好吧 需求開始變更
現(xiàn)在你已經(jīng)完成了開發(fā)工作,數(shù)據(jù)庫使用的Mysql 增刪改查都以實現(xiàn)(你就當(dāng)做已經(jīng)實現(xiàn)了...雖然我并沒有寫增刪改) 項目馬上交付,此時你的領(lǐng)導(dǎo)(XX領(lǐng)導(dǎo)完全不懂技術(shù))電話da來了.
那個誰誰誰 ?最新我聽說大公司都用Oracle數(shù)據(jù)庫 客戶好像都覺得Oracle數(shù)據(jù)庫更靠譜呢 這樣吧, 你也給我換成Oracle數(shù)據(jù)庫
此時你的內(nèi)心應(yīng)該是這樣的



? ?
木頭辦法哎, ?當(dāng)前還是要恰飯的, 服從吧 ?咱小咱卑微 咱是干飯人 咱的錯 咱改
修改數(shù)據(jù)
對于數(shù)據(jù)庫的切換 ?我們知道 ?不僅要修改數(shù)據(jù)庫連接池等相關(guān)的配置 還要修改 相應(yīng)的SQL語句(特定的SQL對于不同的數(shù)據(jù)庫寫法是不一樣滴,分頁就不一樣), 咋辦? 改Dao層吧 于是乎 開始修改項目里所有的Dao實現(xiàn)類 也就是DaoImpl
public?class?DemoDaoImpl?implements?IDemoDao?{
????@Override
????public?List?findAll()? {
????????//?此處為演示方便不連接數(shù)據(jù)庫?模擬返回數(shù)據(jù)庫list結(jié)果
//????????return?Arrays.asList("aaa",?"bbb",?"ccc");
????????//?模擬修改成了Orcale的SQL的語句了
????????return?Arrays.asList("xx領(lǐng)導(dǎo)(Oracle數(shù)據(jù)庫)",?"xx領(lǐng)導(dǎo)(Oracle數(shù)據(jù)庫)",?"xx領(lǐng)導(dǎo)(Oracle數(shù)據(jù)庫)");
????}
}
哈哈 全部檢查一遍 ?自動化測試也執(zhí)行完 ?沒毛病 ?搜易賊... 可以摸魚了...
嘟嘟嘟 ?需求再次變更
項目終于改完要交付了, 干了杯枸杞紅棗茶, 摸魚準備下班中...
那個那個那個啥... ?怎么Oracle數(shù)據(jù)庫要花錢???公司最近客戶還沒有回款 你把數(shù)據(jù)庫還是換回MySQL吧 后面等客戶給回款了我們再考慮用Oracle
此時的你...

哎 再一再二 ?我想大概也會有再三吧 整吧 這次我要想想辦法了 我把你的再三再四直接給你弄上 ?省的影響我摸魚.
引入靜態(tài)工廠
我要創(chuàng)建一個靜態(tài)工廠, 你想要啥數(shù)據(jù)庫我給你整出來一個啥數(shù)據(jù)來
這里暫時起名BeanFactory 別問為啥 我就想起這個名 我卑微我任性
public?class?BeanFactory?{
????public?static?IDemoDao?getDemoDao()?{
????????//?返回?mysql?的?Dao
????????//?return?new?DemoDaoImpl();
????????//?返回?oracle?的?Dao
????????return?new?DemoOracleDaoImpl();
????}
}
改造Service實現(xiàn)即 ServiceImpl
ServiceImpl 中引用的 Dao 不再可以是手動 new 出來的了,
而應(yīng)該由 BeanFactory 的靜態(tài)方法返回來獲得
public?class?DemoServiceImpl?implements?IDemoService?{
//????private?IDemoDao?demoDao?=?new?DemoDaoImpl();
????private?IDemoDao?demoDao?=?BeanFactory.getDemoDao();
????@Override
????public?List?findAll()? {
????????return?demoDao.findAll();
????}
}
好了 即使你再發(fā)生變更(改回Oracle) 也不會影響我摸魚了
我只是改改BeanFactory中的靜態(tài)方法而已
現(xiàn)在終于可以交付項目了, 終于.....
抱歉(并不是你心里所想的會出啥事) 一切順利 老板滿意 客戶滿意
開會要給你先畫個餅 鼓勵一下咯
又出現(xiàn)了新的問題
系統(tǒng)已經(jīng)上線運行 ?這個時候你已經(jīng)著手進行下一個項目了,
可是..可是 領(lǐng)導(dǎo)覺得你的任務(wù)最近不多呀 提出讓你對上一個項目進行優(yōu)化
和擴展的需求, 這時候你再次打開了舊項目 居然發(fā)現(xiàn)項目編譯不通過. 此處為了
演示編譯無法通過,刪除DemoDaoImpl.java
此時你的腦海里一定在翻滾,為么為么為么之前好好的,機器也沒換啊,找到編譯
出錯的類BeanFactory,我x, 我的類去哪兒了,去哪兒了,去哪兒了,三連問之后
少了DemoDaoImpl.java ?導(dǎo)致無法編譯
依賴關(guān)系導(dǎo)致緊耦合
public?class?BeanFactory?{
????public?static?IDemoDao?getDemoDao()?{
????????//?返回?mysql?的?Dao
????????//?因為?DemoDaoImpl.java不存在導(dǎo)致編譯失敗
?????????return?new?DemoDaoImpl();
????????//?返回?oracle?的?Dao
//????????return?new?DemoOracleDaoImpl();
????}
}
BeanFactory類中因 DemoDaoImpl.java 的不存在 而報紅, 導(dǎo)致編譯沒法通過
像當(dāng)前 BeanFactory 強依賴于 DemoDaoImpl (沒有DemoDaoImpl就報紅給你看)
這就是緊耦合
解決緊耦合
沒有這個Java文件 ?我沒法干活呀 要不我自己造一個空的???不行不行
萬一明天 DemoOracleDaoImpl 這個又沒有了呢, 我不能總造空的玩呀
突然此時 你靈光一現(xiàn) 好像 好像 反射大概似乎可能也許可以解決這個問題呢反射可以聲明一個類的全限定類名,進而獲取它的字節(jié)碼,這樣也可以構(gòu)造一個對象
于是乎 開干吧 BeanFactory 就成這樣的了
public?class?BeanFactory?{
????public?static?IDemoDao?getDemoDao()?{
????????try?{
????????????return?(IDemoDao)?Class.forName("com.huodd.dao.impl.DemoDaoImpl").newInstance();
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????????throw?new?RuntimeException("IDemoDao?instantiation?error,?cause:?"?+?e.getMessage());
????????}
????}
}
現(xiàn)在編譯問題得以解決,雖然 DemoService在初始化的時候 還有有問題 但是整個項目
起碼不會無法編譯了
弱依賴的引入
當(dāng)前使用了反射 編譯通過 但是工程啟動后 由于BeanFactory 要構(gòu)造 DemoDaoImpl時確實還沒有該類,所以會拋出ClassNotFoundException但是此時的BeanFactory對DemoDaoImpl 的依賴程度(你愿意丟你就丟 別影響我編譯)
就算的上是 弱依賴了
硬編碼
弱依賴的完成 你找遍電腦的各個盤 終于把 DemoDaoImpl.java 該類給找回來了,這下一切OK
不影響編譯了,不影響運行了,但是好像還哪里不大對呢, 要是再切換數(shù)據(jù)庫呢, 我在工廠類里面
寫死了全限定類名,切換數(shù)據(jù)庫的時候我還得去重新編譯一遍工程才可以正常運行,
應(yīng)該還可以有辦法. 可以考慮下.
引入外部化的配置文件
機智如你呀,終究被你想到了利用IO實現(xiàn)文件的存儲配置 ?每次BeanFactory被初始化時 就讓他去讀取配置文件就好了 我下次就改改配置文件就行了
硬編碼問題得以解決
factory.properties 創(chuàng)建
demoDao=com.huodd.dao.impl.DemoDaoImpl
為方便取到全限定類名,前面是我們給類起的一個"別名"(類似于mybatis的xml中的alias思想) 這樣后面可以直接通過 "別名找到對應(yīng)類的全限定名"
BeanFactory 改造
public?class?BeanFactory?{
????private?static?Properties?properties;
????//?使用靜態(tài)代碼塊初始化properties,加載factord.properties文件
????static?{
????????properties?=?new?Properties();
????????try?{
????????????//?必須使用類加載器讀取resource文件夾下的配置文件
????????????properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
????????}?catch?(IOException?e)?{
????????????//?BeanFactory類的靜態(tài)初始化都失敗了,那后續(xù)也沒有必要繼續(xù)執(zhí)行了
????????????throw?new?ExceptionInInitializerError("BeanFactory?initialize?error,?cause:?"?+?e.getMessage());
????????}
????}
?
?public?static?IDemoDao?getDemoDao()?{
????????String?beanName?=?properties.getProperty("demoDao");
????????try?{
????????????Class>?beanClazz?=?Class.forName(beanName);
????????????return?(IDemoDao)?beanClazz.newInstance();
????????}?catch?(ClassNotFoundException?e)?{
????????????throw?new?RuntimeException("BeanFactory?have?not?["?+?beanName?+?"]?bean!",?e);
????????}?catch?(IllegalAccessException?|?InstantiationException?e)?{
????????????throw?new?RuntimeException("["?+?beanName?+?"]?instantiation?error!",?e);
????????}
????}
}
等等 ?等等 ?這里我們好像又把起的那個別名demoDao給寫死了,得改 干脆傳參吧
要哪個全限定類名參數(shù)傳哪個全限定類名對應(yīng)的別名 此時 getDemoDao這個名字得改還要加參數(shù) 此時
就叫getBean吧 根據(jù)別名獲取相應(yīng)的Bean對象
public?static?IDemoDao?getBean(String?beanAlias)?{
????????String?beanName?=?properties.getProperty("beanAlias");
????????try?{
????????????Class>?beanClazz?=?Class.forName(beanName);
????????????return?(IDemoDao)?beanClazz.newInstance();
????????}?catch?(ClassNotFoundException?e)?{
????????????throw?new?RuntimeException("BeanFactory?have?not?["?+?beanName?+?"]?bean!",?e);
????????}?catch?(IllegalAccessException?|?InstantiationException?e)?{
????????????throw?new?RuntimeException("["?+?beanName?+?"]?instantiation?error!",?e);
????????}
????}
ServiceImpl 改造
DemoServiceImpl 中不能調(diào)用 getDao 方法(讓我們給改成了getBean(String beanAlias))了,
public?class?DemoServiceImpl?implements?IDemoService?{
?IDemoDao?demoDao?=?(IDemoDao)?BeanFactory.getBean("demoDao");
????
?@Override
????public?List?findAll()? {
????????return?demoDao.findAll();
????}
}
到這里,你突然發(fā)現(xiàn)一個現(xiàn)象:這下你可以把所有想抽取出來的組件都可以做成外部化配置了!
PS: 自行去DemoServlet里面把對DemoServiceImpl的緊耦合改正
算了 簡單說一下
在factory.properties外部配置化文件 加入demoService=com.huodd.service.impl.DemoServiceImplDemoServlet獲取DemoServiceImpl改成IDemoService demoService = (IDemoService) BeanFactory.getBean("DemoServiceImpl");
外部配置化
對于這種可能會變化的配置、屬性等,通常不會直接硬編碼在源代碼中, 而是抽取為一些配置文件的形式( properties 、xml 、json 、yml 等), 配合程序?qū)ε渲梦募募虞d和解析,從而達到動態(tài)配置、降低配置耦合的目的。由此大概我們可以知道 原來并不是那些老外拍腦袋隨便就亂搞才出來的這個IoC思想
多重構(gòu)建問題
細心的小伙伴可能已經(jīng)發(fā)現(xiàn) 項目還會存在問題 影響性能的問題 也是很大的問題
就是BeanFactory#getBean(String beanAlias) 會重復(fù)創(chuàng)建同一個對象的多個實例
而我們根本就不需要每次都給我去創(chuàng)建新的對象實例,舊的就夠用了
因此處不是本文重點 ?小伙伴自行改進 ?這里給出一個大概的思路
可以考慮引入緩存 將創(chuàng)建出來的實例緩存起來 每次我們從緩存中讀取
PS: 要多考慮一點哦 別忘了多線程并發(fā)問題
到這里,不知道小伙伴是否對IOC有了個全新的認識和理解呢
這里總結(jié)一下里面出現(xiàn)的幾個關(guān)鍵點
靜態(tài)工廠可將多處依賴進行抽取分離 外部化配置文件+反射可解決配置的硬編碼問題 緩存可控制對象實例數(shù)(這里我們并沒有具體去實現(xiàn)小伙伴自行動手哦)
接下來 ?是否解決了文章開始時小伙伴們的困惑呢?
IOC的思想引入
private?IDemoDao?dao?=?new?DemoDaoImpl();
private?IDemoDao?dao?=?(IDemoDao)?BeanFactory.getBean("demoDao");
對比如上兩種方法獲取dao
前者強依賴/緊耦合 后者弱依賴/松散耦合 前者需保證 DemoDaoImpl存在才能通過編譯 后者無需保證DemoDaoImpl存在就可以通過編譯 倘若factory.properties中聲明的全限定類名出現(xiàn)錯誤, 則會出現(xiàn)ClassCastException
仔細體會下面這種對象獲取的方式,本來咱開發(fā)者可以使用上面的方式,
主動聲明實現(xiàn)類,但如果選擇下面的方式,
那就不再是咱自己去聲明,而是將獲取對象的方式交給了BeanFactory 。這種將控制權(quán)交給別人的思想,就可以稱作:控制反轉(zhuǎn)( Inverse of Control , IOC )。而 BeanFactory 根據(jù)指定的 beanName 去獲取和創(chuàng)建對象的過程,
就可以稱作:依賴查找( Dependency Lookup , DL )。

最近面試BAT,整理一份面試資料《Java面試BAT通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。 獲取方式:點“在看”,關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
