新框架的容器部分終於調通了!容器實在太重要了,所以有用了一個名詞叫“核心容器”。 容器為什麼那麼重要呢?這個有必要好好說道說道。 1、首先我們從框架名稱面向介面編程說起,什麼是面向介面編程?(這個度娘回答一下) 解讀一下:類是個體的定義(建模), 個體的每一方面都可以是一個介面 說白點,其一介面可以 ...
新框架的容器部分終於調通了!容器實在太重要了,所以有用了一個名詞叫“核心容器”。
容器為什麼那麼重要呢?這個有必要好好說道說道。
1、首先我們從框架名稱面向介面編程說起,什麼是面向介面編程?(這個度娘回答一下)
解讀一下:類是個體的定義(建模), 個體的每一方面都可以是一個介面
說白點,其一介面可以代表對象(類)一個方面,再說透點對象可能是多面手(繼承多個介面),能在不同場景(作為不同介面的實例)下正常工作
其二每個介面可以有不同實現,只要實現了這個介面,基本上就可以替換這個位置來正常工作
2、我覺得面向介面編程本質上還是面向對象編程,只是抽象層次更高,更便於擴展和替換
我們需要很多很多的對象來支撐系統的功能需要和擴展需求,還需要每個位置上的對象方便“隨時”替換,擴充
那我們怎麼來管理這個龐大的對象庫呢,如何方便的編排以便管理?這裡就要說到容器了,容器可以很好的勝任這個工作。
3、容器的作用
我認為容器就是倉庫,就是柜子,就是盒子,以便我們可以按自己喜歡的方式存放自己的對象,以便需要的時候能方便的找到他們
好的容器還能維護對象的創建、生命周期、控制反轉(IOC)、依賴註入(DI)、攔截方法執行(AOP)等
前面廢話太多,老規矩先上例子
一、對象的創建和管理由容器負責
1、以下還是前面那篇上下文的例子,使用容器來簡化後的代碼
以上代碼是不是簡單漂亮多了
2、代碼雖然簡單了,功能一點都沒有水分哦,看看以下執行結果截圖
是不是結果上一篇的完全一樣啊。
3、結果一樣不算什麼,改個配置(不改代碼),看看結果
4、再改配置(還是不該代碼)
這次生動了吧,一家人各有各的個性了,是不是很炫啊。
5、大家看看配置
明眼人一眼就能看出這個是Unity容器的配置;有人說,你不是不要依賴Unity容器嗎?我發誓確實沒有依賴Unity容器,但我也沒說不用Unity啊。我只是要做一個框架,其他技術方案都可以集成進來,容器也是。
你熟悉Unity就用Unity,你熟悉Spring.net也可以放心使用,只要你封裝一下並繼承這個框架的容器介面,再註冊進來就可以正常工作了
二、使用了Unity容器,但不依賴Unity容器
1、調用容器的地方沒有依賴Unity
這個測試項目除了系統的幾個引用,就只引用了這個主框架了
2、主框架沒有引用Unity容器
沒騙你們吧?真沒依賴Unity,主框架甚至除了幾個簡單的系統引用,沒有依賴(引用)任何第三方組件,但是這一點都不影響我使用大量第三方的庫,因為這裡是"面向介面可擴展框架"
是不是現在又對介面有了進一步的理解了,是不是“面向介面可擴展框架”這個名字有點名符其實味道了。
三、下麵來解密他到底怎麼運行的
1、看入口,看應用程式(這裡是控制台程式)的引用
哈哈,是不是找到了,終於看到Unity引用了
但是不要被表象所迷惑,其實這裡的主角是Fang.Unity,因為這個項目可以不直接引用Unity容器,Fang.Unity是對Unity容器的封裝。
控制台程式對Unity相關dll是運行時依賴,並不是編譯依賴,這裡直接引用上是為了便於調試
2、繼續探究原理
Fang.Unity總不會是自動運行的吧?當然不是,上代碼
上圖有一句不起眼,但是非常重要的一行代碼“Fang.Unity.ContainerFactory.Init()”,他就是“罪魁禍首”了
換句話話說,Unity容器是完全作為插件在本框架中運行的,如果我不調用Unity的Init,調用Spring.net的Init,那當前所有地方調用的就都是Spring.net容器了。
有人說這也太不方便了吧,當然不是每次使用容器都要調用,如果是web項目可以在global的Application_Start中調用一下就Ok了,也可以在HttpModule的Init中調用也可以。
大部分時候我們在開發中大量使用容器,但並不用想我具體用什麼容器。
換句話說,我熟悉Unity容器,我使用Unity容器配置調試通過的組件給你使用,但是你一直都用Spring.net(或者其他容器),你調用Spring.net容器的封裝,把配置修改為Spring.net的配置,代碼一樣能正常運行
註:前提是組件開發的時候使用的是框架的容器支持,沒有直接引用Unity容器和直接調用Unity容器的代碼
3、還是看一眼Fang.Unity.ContainerFactory.Init是什麼鬼吧(哈哈,要不睡不著了)
Unity容器就不在這裡展開,我會單開一篇講Unity容器和Unity封裝等
四、核心容器解析
1、容器相關實現類截圖
2、核心類圖如下:
稍作解析各個類的分工:
A:IContainer介面是定義容器需要的基本功能
B:IContainerFactory定義容器工廠介面
C:GlobalContainer是個靜態配置類,提供註冊外部容器組件功能及提供調用容器的Api
這個是實現容器插件化的關鍵,這個實現是參考了Asp.net Mvc的DependencyResolver
D:SimpleContainer提供一個簡單和預設的容器和容器工廠實現
AppContext中的上線文容器就是他實現的,如果不配置擴展容器功能,使用的容器就是他了,但是他其實就是一個字典緩存,存個東西,取個東西,完全沒有問題,複雜配置及DI等就沒戲了
E:ContainerWrapper是個容器封裝攔截類,攔截容器的操作,以便增加特性和功能
這個以後再展開講,裡面有一個很有意思的特性
F:ContainerFactoryCacheWrapper是容器工廠封裝及緩存
也就是外面實現的容器工廠是不用考慮單例模式緩存什麼的,他給包辦了,而且把每個生產好的容器對象檢查和打包好
五、多容器的應用
1、我們需要多個容器來編排眾多對象
就好比我從超市一次性買回來很多東西,有雞蛋、排骨、蔬菜、兒童玩具、衣服等等。我不能弄一個大箱子全部放進去,也不亂放,不能把衣服和排骨一起放冰箱(會被媳婦罵死的)。排骨進冰箱冷凍室,雞蛋和蔬菜進冰箱冷藏室。兒童玩具放兒童床。衣服放衣櫃。
代碼做了微調,再看運行結果
有人說,你怎麼越改越複雜啊,代碼複雜一點點,但是配置更加有條理有的時候是值得的,看配置
這隻是一個簡單拆分容器的方案,也可以按對象圖譜來劃分容器,這樣更加合理,因為對象之間有相互的依賴關係,使用容器來做控制反轉也就是這個意思
如果是支持子容器的容器工具(Unity就支持),一個系統就可以按樹狀劃分容器,整個系統的公共配置是根容器,每個子系統都有各自的容器配置,每個子系統的各個模塊也有自己的容器配置,子容器可以繼承父容器也可以覆蓋父容器的配置
2、下麵做一個樹狀子容器的例子
這個效果是不是杠杠的,代碼也是漂亮的一塌糊塗,是怎麼配置的,也看一下吧
總結一下,容器名是優美的鏈式語法,配置文件是樹狀管理結構,Perfect!!!
六、容器擴展
1、SimpleContainer容器不"Simple"
前面有介紹IContainer介面,細心看一下就會發現有添加(Regist)和讀取(Resolve)方法,但是沒有刪除對象的方法,我們要刪除怎麼辦
但是SimpleContainer可是有Remove方法,使用框架的容器功能就要放棄自己更加強大的容器功能是不是有點遺憾,可不可以魚和熊掌兼得?當然可以,上下文那個就是用SimpleContainer實現的,作用域結束就從容器中刪除對象。
我們這裡再舉一個簡單的例子演示一下
使用完整的框架容器支持還是可以使用自定義容器的更多功能技巧就是IContainer介面有一個Provider屬性用來對外暴露原容器對象的Provider屬性,只要把自己想擴展暴露的擴展功能放在Provider屬性中即可
當然也可以和SimpleContainer的Provider直接返回容器本身
細心的用戶可能註意到這個例子裡面出現一個新的東西(Fang.Framework.Factory.Create),這個和前面的GlobalContainer.Factory.Create效果是一樣的,Fang.Framework.Factory是個靜態類,負責把框架的主要工廠方法都彙總起來,以便查找和使用。
但是還是要特別強調一下,這種擴展方式雖然簡單,但是並不推薦使用
因為你一旦對Provider強制類型轉化的時候你就依賴特定的容器實現了,這樣這些的代碼就可能不能適應其他容器,也就是說你的代碼強依賴特定的容器,這樣就違背了本框架的精神。
一句話,你需要更多的特性就會犧牲一些通用性,如果每段代碼都是特例,不能擴展,那這個系統就該有麻煩了。
2、使用ContainerWrapper的擴展
2.1 前面有提到ContainerWrapper時用來包裝容器的,通過框架產生的容器(IContainer)對象都是被ContainerWrapper包裝過的,而且ContainerWrapper攔截IContainer所有的方法
只要定義一個容器包裝類繼承ContainerWrapper,重寫自己想要擴展的功能,定義一個ContainerFactory類(繼承IContainerFactory)返回的包裝後的容器對象,框架裡面有調用一個CheckWrapper方法處理新產生的容器對象,如果對象可以轉化為ContainerWrapper對象就不需要再次包裝了
2.2 還有一個擴展技巧就是先按沒有擴展的方式註冊容器工廠
然後對容器工廠再次擴展返回ContainerWrapper對象,也就是自己定義容器封裝功能代替框架預設的封裝邏輯
以後可能會開發一個通用DI標註擴展功能,就打算使用這種方式,獲取對象的時候檢查當前對象或者類型是否有對應標註(Attribute),如果有就調用特殊邏輯處理,沒有統一的DI標註,使用DI就不得不依賴特定容器,這樣就會影響代碼復用性和可遷移性。
七、容器"黑科技"
1、快速檢索
用黑科技來改造樹狀結構的例子
可以配置很多容器來管理對象,但調用我們卻不用直接和每個容器打交道,只要按一定規則命名,可以使用任一個容器對象來調取任一個對象配置
如果以上黑科技再配合上DI,那就更Perfect了!!!
註:這裡面有一個插曲,一個工程師使用容器配置DI,他指著一個配置文件裡面的一個節點對我說,我想要把這個節點DI到我的Controller上。我對他解釋,"DI不能這樣的,你必須把這個節點放在根容器中,或者定義一個分區並指向一個容器,然後把這個節點複製到那個新容器中"。這個工程師聽得滿頭霧水,迷茫的說"我只是想把這個節點映射到我的Controller上,...";有了這個黑科技,我可以放心告訴他,你寫一個DI標註就可以了
就寫這麼多了,還有很多東西要寫,也有很多功能待開發,以後再補充吧。
提供測試源代碼下載。
特別強調,本框架還在開發中,並沒有進行完全測試,不排除有重大bug的可能性,切忌暫時不要拿到自己現實項目中,出現問題後果自負,而且本框架還沒有正式開源,沒有開源授權。