前言
本文IOC方面的內(nèi)容借鑒了知乎上一位用戶的回答解虱,感覺比較直觀塞琼,鏈接:https://www.zhihu.com/question/23277575/answer/169698662
IOC
要了解控制反轉(zhuǎn)( Inversion of Control ), 我覺得有必要先了解軟件設(shè)計(jì)的一個(gè)重要思想:依賴倒置原則(Dependency Inversion Principle )。
什么是依賴倒置原則?假設(shè)我們設(shè)計(jì)一輛汽車:先設(shè)計(jì)輪子逼纸,然后根據(jù)輪子大小設(shè)計(jì)底盤逸邦,接著根據(jù)底盤設(shè)計(jì)車身互广,最后根據(jù)車身設(shè)計(jì)好整個(gè)汽車哑姚。這里就出現(xiàn)了一個(gè)“依賴”關(guān)系:汽車依賴車身,車身依賴底盤瓮床,底盤依賴輪子盹舞。
這樣的設(shè)計(jì)看起來沒問題,但是可維護(hù)性卻很低隘庄。假設(shè)設(shè)計(jì)完工之后踢步,上司卻突然說根據(jù)市場需求的變動,要我們把車子的輪子設(shè)計(jì)都改大一碼丑掺。這下我們就蛋疼了:因?yàn)槲覀兪歉鶕?jù)輪子的尺寸設(shè)計(jì)的底盤获印,輪子的尺寸一改,底盤的設(shè)計(jì)就得修改街州;同樣因?yàn)槲覀兪歉鶕?jù)底盤設(shè)計(jì)的車身兼丰,那么車身也得改玻孟,同理汽車設(shè)計(jì)也得改——整個(gè)設(shè)計(jì)幾乎都得改!
我們現(xiàn)在換一種思路鳍征。我們先設(shè)計(jì)汽車的大概樣子黍翎,然后根據(jù)汽車的樣子來設(shè)計(jì)車身,根據(jù)車身來設(shè)計(jì)底盤艳丛,最后根據(jù)底盤來設(shè)計(jì)輪子匣掸。這時(shí)候,依賴關(guān)系就倒置過來了:輪子依賴底盤氮双, 底盤依賴車身碰酝, 車身依賴汽車。
這時(shí)候戴差,上司再說要改動輪子的設(shè)計(jì)送爸,我們就只需要改動輪子的設(shè)計(jì),而不需要動底盤暖释,車身碱璃,汽車的設(shè)計(jì)了。
這就是依賴倒置原則——把原本的高層建筑依賴底層建筑“倒置”過來饭入,變成底層建筑依賴高層建筑嵌器。高層建筑決定需要什么,底層去實(shí)現(xiàn)這樣的需求谐丢,但是高層并不用管底層是怎么實(shí)現(xiàn)的爽航。這樣就不會出現(xiàn)前面的“牽一發(fā)動全身”的情況。
控制反轉(zhuǎn)(Inversion of Control) 就是依賴倒置原則的一種代碼設(shè)計(jì)的思路乾忱。具體采用的方法就是所謂的依賴注入(Dependency Injection)讥珍。其實(shí)這些概念初次接觸都會感到云里霧里的。說穿了窄瘟,這幾種概念的關(guān)系大概如下:
為了理解這幾個(gè)概念衷佃,我們還是用上面汽車的例子。只不過這次換成代碼蹄葱。我們先定義四個(gè)Class氏义,車,車身图云,底盤惯悠,輪胎。然后初始化這輛車竣况,最后跑這輛車克婶。代碼結(jié)構(gòu)如下:
這樣,就相當(dāng)于上面第一個(gè)例子,上層建筑依賴下層建筑——每一個(gè)類的構(gòu)造函數(shù)都直接調(diào)用了底層代碼的構(gòu)造函數(shù)情萤。假設(shè)我們需要改動一下輪胎(Tire)類鸭蛙,把它的尺寸變成動態(tài)的,而不是一直都是30筋岛。我們需要這樣改:
由于我們修改了輪胎的定義娶视,為了讓整個(gè)程序正常運(yùn)行,我們需要做以下改動:
由此我們可以看到泉蝌,僅僅是為了修改輪胎的構(gòu)造函數(shù)歇万,這種設(shè)計(jì)卻需要修改整個(gè)上層所有類的構(gòu)造函數(shù)揩晴!在軟件工程中勋陪,這樣的設(shè)計(jì)幾乎是不可維護(hù)的——在實(shí)際工程項(xiàng)目中,有的類可能會是幾千個(gè)類的底層硫兰,如果每次修改這個(gè)類诅愚,我們都要修改所有以它作為依賴的類,那軟件的維護(hù)成本就太高了劫映。
所以我們需要進(jìn)行控制反轉(zhuǎn)(IoC)违孝,及上層控制下層,而不是下層控制著上層泳赋。我們用依賴注入(Dependency Injection)這種方式來實(shí)現(xiàn)控制反轉(zhuǎn)雌桑。所謂依賴注入,就是把底層類作為參數(shù)傳入上層類祖今,實(shí)現(xiàn)上層類對下層類的“控制”校坑。這里我們用構(gòu)造方法傳遞的依賴注入方式重新寫車類的定義:
這里我們再把輪胎尺寸變成動態(tài)的,同樣為了讓整個(gè)系統(tǒng)順利運(yùn)行千诬,我們需要做如下修改:
看到?jīng)]耍目?這里我只需要修改輪胎類就行了,不用修改其他任何上層類徐绑。這顯然是更容易維護(hù)的代碼邪驮。不僅如此,在實(shí)際的工程中傲茄,這種設(shè)計(jì)模式還有利于不同組的協(xié)同合作和單元測試:比如開發(fā)這四個(gè)類的分別是四個(gè)不同的組毅访,那么只要定義好了接口,四個(gè)不同的組可以同時(shí)進(jìn)行開發(fā)而不相互受限制盘榨;而對于單元測試俺抽,如果我們要寫Car類的單元測試,就只需要Mock一下Framework類傳入Car就行了较曼,而不用把Framework, Bottom, Tire全部new一遍再來構(gòu)造Car磷斧。
???這里我們是采用的構(gòu)造函數(shù)傳入的方式進(jìn)行的依賴注入。其實(shí)還有另外兩種方法:Setter傳遞和接口傳遞。這里就不多講了弛饭,核心思路都是一樣的冕末,都是為了實(shí)現(xiàn)控制反轉(zhuǎn)。
???IoC是如何實(shí)現(xiàn)的侣颂。想象一下如果我們自己來實(shí)現(xiàn)這個(gè)依賴注入的功能档桃,我們怎么來做?
???無外乎:讀取標(biāo)注或者配置文件憔晒,拿到類名使用反射的API
基于類名實(shí)例化對應(yīng)的對象實(shí)例將對象實(shí)例藻肄,
通過構(gòu)造函數(shù)或者setter,傳遞
???我們發(fā)現(xiàn)其實(shí)自己來實(shí)現(xiàn)也不是很難拒担,Spring實(shí)際也就是這么做的嘹屯。這么看的話其實(shí)IoC就是一個(gè)工廠模式的升級版!當(dāng)然要做一個(gè)成熟的IoC框架从撼,還是非常多細(xì)致的工作要做州弟,Spring不僅提供了一個(gè)已經(jīng)成為業(yè)界標(biāo)準(zhǔn)的Java IoC框架,還提供了更多強(qiáng)大的功能.
AOP
???AOP可以說是對OOP的補(bǔ)充和完善低零。OOP引入封裝婆翔、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合掏婶。當(dāng)我們需要為分散的對象引入公共行為的時(shí)候啃奴,OOP則顯得無能為力。也就是說雄妥,OOP允許你定義從上到下的關(guān)系最蕾,但并不適合定義從左到右的關(guān)系。例如日志功能茎芭。日志代碼往往水平地散布在所有對象層次中揖膜,而與它所散布到的對象的核心功能毫無關(guān)系。在OOP設(shè)計(jì)中梅桩,它導(dǎo)致了大量代碼的重復(fù)壹粟,而不利于各個(gè)模塊的重用。
???實(shí)現(xiàn)AOP的技術(shù)宿百,主要分為兩大類:一是采用動態(tài)代理技術(shù)趁仙,利用截取消息的方式,對該消息進(jìn)行裝飾垦页,以取代原有對象行為的執(zhí)行雀费;二是采用靜態(tài)織入的方式,引入特定的語法創(chuàng)建“方面”痊焊,從而使得編譯器可以在編譯期間織入有關(guān)“方面”的代碼.
???AOP實(shí)現(xiàn)原理其實(shí)是java動態(tài)代理盏袄,但是jdk的動態(tài)代理必須實(shí)現(xiàn)接口忿峻,所以spring的AOP是用cglib這個(gè)庫實(shí)現(xiàn)的,cglib使用了asm這個(gè)直接操縱字節(jié)碼的框架辕羽,所以可以做到不實(shí)現(xiàn)接口的情況下完成動態(tài)代理逛尚。