首頁>技術>

看完此文,別說你不懂IoC是什麼?

盆友圈都在曬十八歲,

真羨慕你們可以發18歲的照片,我還要再等兩年才能發。哎~惆悵..

前言

很多小夥伴會對Spring的IoC有些疑惑,什麼是控制反轉?

我為何要使用IoC 把控制權交給容器這樣對我有什麼好處?

書上只講理論, 我現在都不能體會Spring的IoC用於不用相比有什麼好處,

由spring託管有什麼好處呢?

我現在感覺用spring 的set注入就是看起來程式碼NB點,

完全不理解到底有什麼優勢啊……

基於如上讓你徹底搞定理解IoC,從此不再困惑

廢話不多說(正文依舊有),我們開始吧!

原生Servlet

我們先從原生Servlet時代的三層架構(MVC)開始切入

建立maven工程 引入依賴
 <dependency>    <groupId>javax.servlet</groupId>    <artifactId>javax.servlet-api</artifactId>    <version>3.1.0</version>    <scope>provided</scope></dependency>
建立測試
@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)

瀏覽器可輸出 PoXingServletrun...... 此時已完成基礎Servlet

進一步最佳化 新增 Service 和 Dao

這裡為演示簡單 並未引入輸入庫相關依賴 故此處 Dao 只是模擬有資料庫的存在,

不深入

工程目錄建立以下類與介面

三層架構中元件與依賴應如下圖所示

新增Dao
public interface IDemoDao {    List<String> findAll();}
public class DemoDaoImpl implements IDemoDao {    @Override    public List<String> findAll() {        // 此處為演示方便不連線資料庫 模擬返回資料庫list結果        return Arrays.asList("aaa", "bbb", "ccc");    }}
新增Service
public interface IDemoService {    List<String> findAll();}
public class DemoServiceImpl implements IDemoService {    private IDemoDao demoDao = new DemoDaoImpl();    @Override    public List<String> findAll() {        return demoDao.findAll();    }}
修改DemoServlet

將我們前面建立的 PoXingServlet 內容複製 並加以修改

@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'] 此時已完成基礎Servlet

基礎搭建工作完成,下面才是我們的重點! 我們的重點!! 我們的重點!!!

好吧 需求開始變更

現在你已經完成了開發工作,資料庫使用的Mysql

增刪改查都以實現(你就當做已經實現了...雖然我並沒有寫增刪改)

專案馬上交付,此時你的領導(XX領導完全不懂技術)電話da來了.

那個誰誰誰 最新我聽說大公司都用Oracle資料庫

客戶好像都覺得Oracle資料庫更靠譜呢

這樣吧, 你也給我換成Oracle資料庫

此時你的內心應該是這樣的 XX領導, XX不幹了,

木頭辦法哎, 找下家太麻煩, 當前還是要恰飯的, 服從吧 咱小咱卑微 咱是乾飯人 咱的錯

咱改

修改資料

對於資料庫的切換 我們知道 不僅要修改資料庫連線池等相關的配置 還要修改

相應的SQL語句(特定的SQL對於不同的資料庫寫法是不一樣滴,分頁就不一樣), 咋辦?

改Dao層吧 於是乎 開始修改專案裡所有的Dao實現類 也就是DaoImpl

public class DemoDaoImpl implements IDemoDao {    @Override    public List<String> findAll() {        // 此處為演示方便不連線資料庫 模擬返回資料庫list結果//        return Arrays.asList("aaa", "bbb", "ccc");        // 模擬修改成了Orcale的SQL的語句了        return Arrays.asList("xx領導(Oracle資料庫)", "xx領導(Oracle資料庫)", "xx領導(Oracle資料庫)");    }}

哈哈 全部檢查一遍 自動化測試也執行完 沒毛病 搜易賊...

可以摸魚了...

嘟嘟嘟 需求再次變更

專案終於改完要交付了, 乾了杯枸杞紅棗茶, 摸魚準備下班中...

那個那個那個啥... 怎麼Oracle資料庫要花錢??? 公司最近客戶還沒有回款

你把資料庫還是換回MySQL吧 後面等客戶給回款了我們再考慮用Oracle

此時的你...

哎 再一再二 我想大概也會有再三吧 整吧 這次我要想想辦法了

我把你的再三再四直接給你弄上 省的影響我摸魚.

引入靜態工廠

我要建立一個靜態工廠, 你想要啥資料庫我給你整出來一個啥資料來

這裡暫時起名 BeanFactory 別問為啥 我就想起這個名 我卑微我任性

public class BeanFactory {    public static IDemoDao getDemoDao() {        // 返回 mysql 的 Dao        // return new DemoDaoImpl();        // 返回 oracle 的 Dao        return new DemoOracleDaoImpl();    }}
改造Service實現即 ServiceImpl

ServiceImpl 中引用的 Dao 不再可以是手動 new 出來的了,

而應該由 BeanFactory 的靜態方法返回來獲得

public class DemoServiceImpl implements IDemoService {//    private IDemoDao demoDao = new DemoDaoImpl();    private IDemoDao demoDao = BeanFactory.getDemoDao();    @Override    public List<String> findAll() {        return demoDao.findAll();    }}

好了 即使你再發生變更(改回Oracle) 也不會影響我摸魚了

我只是改改BeanFactory中的靜態方法而已

現在終於可以交付專案了, 終於.....

抱歉(並不是你心裡所想的會出啥事) 一切順利 老闆滿意 客戶滿意

開會要給你先畫個餅 鼓勵一下咯

又出現了新的問題

系統已經上線執行 這個時候你已經著手進行下一個專案了,

可是..可是 領導覺得你的任務最近不多呀 提出讓你對上一個專案進行最佳化

和擴充套件的需求, 這時候你再次打開了舊專案 居然發現專案編譯不透過. 此處為了

此時你的腦海裡一定在翻滾,為麼為麼為麼之前好好的,機器也沒換啊,找到編譯

出錯的類 BeanFactory,我x, 我的類去哪兒了,去哪兒了,去哪兒了,三連問之後

少了 DemoDaoImpl.java 導致無法編譯

依賴關係導致緊耦合
public class BeanFactory {    public static IDemoDao getDemoDao() {        // 返回 mysql 的 Dao        // 因為 DemoDaoImpl.java不存在導致編譯失敗         return new DemoDaoImpl();        // 返回 oracle 的 Dao//        return new DemoOracleDaoImpl();    }}

BeanFactory類中因 DemoDaoImpl.java 的不存在 而報紅, 導致編譯沒法透過

像當前 BeanFactory 強依賴於 DemoDaoImpl (沒有 DemoDaoImpl就報紅給你看)

這就是緊耦合

解決緊耦合

沒有這個Java檔案 我沒法幹活呀 要不我自己造一個空的??? 不行不行

萬一明天 DemoOracleDaoImpl 這個又沒有了呢, 我不能總造空的玩呀

突然此時 你靈光一現 好像 好像 反射大概似乎可能也許可以解決這個問題呢

反射可以宣告一個類的全限定類名,進而獲取它的位元組碼,這樣也可以構造一個物件

於是乎 開幹吧 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());        }    }}

現在編譯問題得以解決,雖然 DemoService在初始化的時候 還有有問題 但是整個專案

起碼不會無法編譯了

弱依賴的引入

當前使用了反射 編譯透過 但是工程啟動後 由於 BeanFactory 要構造 DemoDaoImpl

時確實還沒有該類,所以會丟擲 ClassNotFoundException

但是此時的 BeanFactory對 DemoDaoImpl 的依賴程度(你願意丟你就丟 別影響我編譯)

就算的上是 弱依賴

硬編碼

弱依賴的完成 你找遍電腦的各個盤 終於把 DemoDaoImpl.java 該類給找回來了,這下一切OK

不影響編譯了,不影響運行了,但是好像還哪裡不大對呢, 要是再切換資料庫呢, 我在工廠類裡面

寫死了全限定類名,切換資料庫的時候我還得去重新編譯一遍工程才可以正常執行,

應該還可以有辦法. 可以考慮下.

引入外部化的配置檔案

機智如你呀,終究被你想到了利用IO實現檔案的儲存配置 每次 BeanFactory

被初始化時 就讓他去讀取配置檔案就好了 我下次就改改配置檔案就行了

硬編碼問題得以解決

factory.properties 建立
demoDao=com.huodd.dao.impl.DemoDaoImpl

為方便取到全限定類名,前面是我們給類起的一個"別名"(類似於mybatis的xml中的alias思想)

這樣後面可以直接透過 "別名找到對應類的全限定名"

BeanFactory 改造
public class BeanFactory {    private static Properties properties;    // 使用靜態程式碼塊初始化properties,載入factord.properties檔案    static {        properties = new Properties();        try {            // 必須使用類載入器讀取resource資料夾下的配置檔案            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));        } catch (IOException e) {            // BeanFactory類的靜態初始化都失敗了,那後續也沒有必要繼續執行了            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給寫死了,得改 乾脆傳參

要哪個全限定類名引數傳哪個全限定類名對應的別名 此時 getDemoDao這個名字得改還要加引數 此時

就叫 getBean吧 根據別名獲取相應的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 中不能呼叫 getDao 方法(讓我們給改成了 getBean(StringbeanAlias))了,

public class DemoServiceImpl implements IDemoService {    IDemoDao demoDao = (IDemoDao) BeanFactory.getBean("demoDao");    @Override    public List<String> findAll() {        return demoDao.findAll();    }}

到這裡,你突然發現一個現象:

這下你可以把所有想抽取出來的元件都可以做成外部化配置了!

PS: 自行去 DemoServlet裡面把對 DemoServiceImpl的緊耦合改正

算了 簡單說一下

在 factory.properties 外部配置化檔案 加入 demoService=com.huodd.service.impl.DemoServiceImpl

DemoServlet獲取 DemoServiceImpl改成 IDemoServicedemoService=(IDemoService)BeanFactory.getBean("DemoServiceImpl");

外部配置化

對於這種可能會變化的配置、屬性等,通常不會直接硬編碼在原始碼中,

而是抽取為一些配置檔案的形式( properties 、xml 、json 、yml 等),

配合程式對配置檔案的載入和解析,從而達到動態配置、降低配置耦合的目的。

由此大概我們可以知道 原來並不是那些老外拍腦袋隨便就亂搞才出來的這個IoC思想

多重構建問題

細心的小夥伴可能已經發現 專案還會存在問題 影響效能的問題 也是很大的問題

就是 BeanFactory#getBean(String beanAlias) 會重複建立同一個物件的多個例項

而我們根本就不需要每次都給我去建立新的物件例項,舊的就夠用了

因此處不是本文重點 小夥伴自行改進 這裡給出一個大概的思路

可以考慮引入快取 將創建出來的例項快取起來 每次我們從快取中讀取

PS: 要多考慮一點哦 別忘了多執行緒併發問題

到這裡,不知道小夥伴是否對IOC有了個全新的認識和理解呢

這裡總結一下里面出現的幾個關鍵點

靜態工廠可將多處依賴進行抽取分離外部化配置檔案+反射可解決配置的硬編碼問題快取可控制物件例項數(這裡我們並沒有具體去實現小夥伴自行動手哦)

接下來 是否解決了文章開始時小夥伴們的困惑呢?

IOC的思想引入
private IDemoDao dao = new DemoDaoImpl();private IDemoDao dao = (IDemoDao) BeanFactory.getBean("demoDao");

對比如上兩種方法獲取 dao

前者強依賴/緊耦合 後者弱依賴/鬆散耦合前者需保證 DemoDaoImpl存在才能透過編譯後者無需保證 DemoDaoImpl存在就可以透過編譯 倘若 factory.properties 中宣告的全限定類名出現錯誤,則會出現 ClassCastException

仔細體會下面這種物件獲取的方式,本來咱開發者可以使用上面的方式,

主動宣告實現類,但如果選擇下面的方式,

那就不再是咱自己去宣告,而是將獲取物件的方式交給了 BeanFactory 。

這種將控制權交給別人的思想,就可以稱作:控制反轉( Inverse of Control , IOC )

而 BeanFactory 根據指定的 beanName 去獲取和建立物件的過程,

就可以稱作:依賴查詢( Dependency Lookup , DL )

21
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 安裝Jupyter Notebook並執行對抗神經網路