閱讀929 返回首頁    go 阿裏雲 go 技術社區[雲棲]


你知道Unity IoC Container是如何創建對象的嗎?

Unity是微軟P&P推出的一個開源的IoC框架,最新的官方版本是2.0。Unity之前的版本建立在一個稱為ObjectBuild的組件上,熟悉EnterLib的讀者,相信對ObjectBuild不會感到陌生。對於EnterLib 5.0之前的版本,ObjectBuild可以說是所有Application Block的基石。ObjectBuild提供一種擴展、可定製的對象創建方式,雖然微軟官方沒有將ObjectBuild和IoC聯係在一起,其本質可以看成是一個IoC框架。在Unity 2.0中,微軟直接將ObjectBuild(實際上是ObjectBuild的第二個版本ObjectBuild2)的絕大部分功能實現在了Unity中,而EnterLib則直接建立在Unity之上。由此可見Unity在EnterLib以及微軟其他一些開源框架(比如Software Factory)中的重要地位。

之前園子裏也有一些介紹EnterLib的文章,其中也不乏對Unity/ObjectBuild的介紹。雖然微軟官方聲稱Unity是一個輕量級的IoC框架,但是並不意味著Unity會很簡單。也正是因為Unity/ObjectBuild的複雜性,很多人撰文介紹Unity/ObjectBuild的時候,往往為了麵麵俱到,導致很多讀者不知所雲。最終的結果是,了解Unity/ObjectBuild的讀者能夠看懂,不懂的人讀了還是不懂。在本篇文章中,我試著換一種介紹方式:抓住Unity/ObjectBuild最本質的東西,剔除一些細枝末節,希望以一種全新的視角讓讀者了解Unity的本質。

一、從管道+上下文(Pipeline+Context)模式說起

如果要說Unity Container采用的怎樣的設計/架構模式的話,我的回答是“管道+上下文(Pipeline + Context)模式”(我不知道是否真的具有這樣一種叫法)。在《WCF技術剖析(卷1)》中介紹Binding一章中,我曾經對該模式作了一個類比:“比如有一個為居民提供飲用水的自來水廠,它的任務就是抽取自然水源,進行必要的淨化處理,最終輸送到居民區。淨化處理的流程可能是這樣的:天然水源被汲取到一個蓄水池中先進行雜質的過濾(我們稱這個池為過濾池);被過濾後的水流到第二個池子中進行消毒處理(我們稱這個池為消毒池);被消毒處理的水流到第三個池子中進行水質軟化處理(我們稱這個池為軟化池);最終水通過自來水管道流到居民的家中。”

當我們需要創建一個基礎架構對某種元素(例子中需要進行處理的水)進行一係列處理的時候,我們就可以將相應的處理邏輯(例子中的過濾、消毒和軟化)實現在相應“節點”(例子中的過濾池、消毒池和軟化池 )中。根據需要(比如水質情況)對相應的節點進行有序組合(水質的不同決定了處理工序的差異)從而構成一個管道(自來水廠整個水處理管道)。由於每一個節點具有標準的接口,我們可以對組成管道的各個節點具有任意重組,也可以為某種需要自定義節點,從而使我們的“管道”變得能夠適應所有的處理需要。

對於這樣的設計,其實我們並不陌生。比如ASP.NET的運行時就可以看成是一個由若幹HttpModule組成的處理HTTP請求的管道,WCF中Binding就是一個由若幹信道(Channel)組成的處理Message的管道。相同的設計還體現在.NET Remoting, BizTalk等相關框架和產品的設計上。

基於相應標準的“節點”進行有序組合構成管道,但是各個相對獨立的節點如何進行相應的協作呢?這就需要在整個管道範圍內共享一些上下文(Context),上下文是對管道處理對象和處理環境的封裝。ASP.NET運行時管道的上下文對象是HttpContext,而Binding管道的上下文是BindingContext。

二、UnityContainer是BuildStrategy的管道

image

作為一個IoC框架,Unity Container的最終目的動態地解析和注入依賴,最終提供(創建新對象或者提供現有對象)一個符合你要求的對象。為了讓整個對象提供處理流程變得可擴展和可訂製,整個處理過程被設計成一個管道。管道的每一個節點被稱為BuilderStrategy,它們按照各自的策略參與到整個對象提供處理流程之中。

除了對象的提供功能之外,Unity Container還提供另一個相反的功能:對象的回收。我們暫且將兩者稱之為Build-Up和Tear-Down。Build-Up和Tear-Down采用相同的處理機製。

左圖反映的就是Unity Container由若幹BuilderStrategy組成的一個用於進行對象的Build-Up和Tear-Down的管道。每一個BuildStrategy具有相同的接口(這個接口是IBuilderStrategy),它們具有四個標準的方法:PreBuildUp、PostBuildUp、PreTearDown和PostTearDown。從名稱我們不難看出,四個方法分別用於完成對象創建前/後和對象回收前後相應的操作。具體來說,當需要利用該管道創建某個對象的時候,先按照BuilderStrategy在管道中的順序調用PreBuildUp方法,到管道底端後,按照相反的順序調用PostBuildUp方法。

對於組成Unity Container管道的各個BuilderStrategy來說,它們彼此是相互獨立的,一個BuilderStrategy隻需要完成基於自身策略相應的操作,不需要知道其他BuilderStrategy的存在。隻有這樣才能實現對管道的靈活定製,真正實現可擴展。但是在真正工作的時候,彼此之間需要共享一些上下文以促進相互協作。在這裏,BuilderContext起到了這樣的作用。在Unity中,BuilderContext實現了IBuilderContext,我們不妨來看看IBuilderContext定義了些什麼:

   1: public interface IBuilderContext
   2: {
   3:     //Others...
   4:     bool BuildComplete { get; set; }
   5:     NamedTypeBuildKey BuildKey { get; set; }
   6:     NamedTypeBuildKey OriginalBuildKey { get; }
   7:     object CurrentOperation { get; set; }
   8:     object Existing { get; set; }
   9: }

上麵對IBuilderContext的定義中,簡單起見,我刻意省略了一些屬性。在上述的屬性列表中,BuildComplete表示Build操作是否被標識為結束,如果某個BuilderStrategy已經完成了Build的操作,可以將其設置為True,這樣後續的BuilderStrategy就可以根據該值進行相應的操作(大部分將不作進行後續的Build);BuildKey和OriginalBuildKey是一個以Type + Name為組合的代表當前Build操作(通過CurrentOperation表示)的鍵;Existing屬性表示已經生成的對象,一般來講BuilderStrategy會將自己生成的對象賦給該值;而Strategies則代表了整個BuilderStrategy管道。

三、創建一個最簡單的BuilderStrategy

現在我們編寫一個最簡單不過的例子,看看UnityContainer是如何借助於BuilderStrategy管道進行對象的提供的(你可以通過這裏下載源代碼)。我們先來創建一個最簡單不過的BuilderStrategy,我們的策略就是通過反射的方式來創建對象,為此我們將該BuilderStrategy命名為ReflectionBuilderStrategy。

   1: public class ReflectionBuilderStrategy:BuilderStrategy
   2: {
   3:     public override void PreBuildUp(IBuilderContext context)
   4:     {
   5:         if (context.BuildComplete || null != context.Existing)
   6:         {
   7:             return;
   8:         }
   9:         var value = Activator.CreateInstance(context.BuildKey.Type);
  10:         if (null != value)
  11:         {
  12:             context.Existing = value;
  13:             context.BuildComplete = true;
  14:         }
  15:     }
  16: }

ReflectionBuilderStrategy繼承自統一的基類BuilderStrategy。由於我們隻需要ReflectionBuildStrategy進行對象的創建,這裏我們隻需要重寫PreBuildUp方法。在PreBuildUp方法中,如果需要提供的對象已經存在(通過BuilderContext的Existing屬性判斷)或者Build操作已經完成(通過BuilderContext的BuildComplete屬性判斷),則直接返回。否則通過BuilderContext的BuildKey屬性得到需要創建對象的類型,通過反射的機製創建該對象,將其賦給BuilderContext的Existing屬性,並將BuildComplete設置成True。

現在BuilderStrategy已經創建成功,如何將它添加到UnityContainer的BuilderStrategy管道呢?一般地,我們需要為BuilderStrategy創建相應的擴展對象。為此,下麵我們創建了一個繼承自UnityContainerExtension的類型ReflectionContainerExtension:

   1: public class ReflectionContainerExtension : UnityContainerExtension
   2: {
   3:     protected override void Initialize()
   4:     {
   5:         this.Context.Strategies.AddNew<ReflectionBuilderStrategy>(UnityBuildStage.PreCreation);
   6:     }
   7: }

在ReflectionContainerExtension中,我僅僅重寫了Initialize方法。在該方法中,通過Context屬性得到相應UnityContainer的BuilderStrategy管道,並調用AddNew方法將我們創建的ReflectionBuilderStrategy添加進取。泛型方法AddNew接受一個UnityBuildStage類型的枚舉。UnityBuildStage代表整個Build過程的某個階段,在這裏決定了添加的BuilderStrategy在管道中的位置。現在我們假設需要通過UnityContainer來創建下麵一個類型為Foo的對象:

   1: public class Foo
   2: {
   3:     public Guid Id { get; private set; }
   4:  
   5:     public Foo()
   6:     {
   7:         Id = Guid.NewGuid();
   8:     }
   9: }

真正通過UnityContainer進行對象的提供實現在下麵的代碼中。由於UnityContainer在初始化的過程中會通過UnityDefaultStrategiesExtension這麼一個擴展,所以我特意通過調用RemoveAllExtension將其清除。然後調用AddExtension將我們上麵創建的ReflectionContainerExtension添加到UnityContainer的擴展列表中。最後3次調用UnityContainer的Resolve方法得到Foo對象,並將ID輸出。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     container.RemoveAllExtensions();
   5:     container.AddExtension(new ReflectionContainerExtension());
   6:     Console.WriteLine(container.Resolve<Foo>().Id);
   7:     Console.WriteLine(container.Resolve<Foo>().Id);
   8:     Console.WriteLine(container.Resolve<Foo>().Id);            
   9: }
  10:     }

下麵是輸出結果:

b38aa0b4-cc69-4d16-9f8c-8ea7baf1d853
ef0cefc2-ffac-4488-ad96-907fb568360b
08c538df-e208-4ef9-abe0-df7841d7ab60

四、通過自定義BuilderStrategy實現單例模式

上麵的例子已經能夠基本上反映出UnityContainer借助於BuilderStrategy管道的對象提供機製了。為了更加進一步的說明“管道”的存在,我們再自定義另一個簡單的BuilderStrategy,實現我們熟悉的單例模式(基於UnityContainer對象來說是單例)。下麵是是實現單例模式的BuilderStrategy:SingletonBuilderStrategy,和相應的Unity擴展。在SingletonBuilderStrategy中,我們通過一個靜態字典用於緩存創建成功的對象,該對象在字典中的Key為創建對象的類型。被創建的對象在PostCreate方法中被緩存,在PreCreate中被返回。為了將該SingletonBuilderStrategy至於管道的前端,在添加的時候指定的UnityBuildStage為Setup。

   1: public class SingletonBuilderStrategy : BuilderStrategy
   2: {
   3:     private static IDictionary<Type, object> cachedObjects = new Dictionary<Type, object>();
   4:  
   5:     public override void PreBuildUp(IBuilderContext context)
   6:     {
   7:         if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type))
   8:         {
   9:             context.Existing = cachedObjects[context.BuildKey.Type];
  10:             context.BuildComplete = true;
  11:         }
  12:     }
  13:  
  14:     public override void PostBuildUp(IBuilderContext context)
  15:     {
  16:         if (cachedObjects.ContainsKey(context.OriginalBuildKey.Type) || null == context.Existing)
  17:         {
  18:             return;
  19:         }
  20:  
  21:         cachedObjects[context.OriginalBuildKey.Type] = context.Existing;
  22:     }
  23: }
  24:  
  25: public class SingletonContainerExtension : UnityContainerExtension
  26: {
  27:     protected override void Initialize()
  28:     {
  29:         this.Context.Strategies.AddNew<SingletonBuilderStrategy>(UnityBuildStage.Setup);
  30:     }
  31: }

現在,我們將基於SingletonBuilderStrategy的擴展添加到之前的程序中。再次運行我們的程序,你會發現輸出的ID都是一樣的,由此可見三次創建的對象均是同一個。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         IUnityContainer container = new UnityContainer();
   6:         container.RemoveAllExtensions();
   7:         container.AddExtension(new ReflectionContainerExtension());
   8:         container.AddExtension(new SingletonContainerExtension());
   9:         Console.WriteLine(container.Resolve<Foo>().Id);
  10:         Console.WriteLine(container.Resolve<Foo>().Id);
  11:         Console.WriteLine(container.Resolve<Foo>().Id);            
  12:     }
  13: }

輸出結果:

254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42
254e1636-56e7-42f7-ad03-493864824d42

總結:雖然Unity具體的實現機製相對複雜,但是其本質就是本文所介紹的基於BuilderStrategy+BuilderContext的Pipeline+Context的機製。當你在研究Unity的具體實現原理的時候,抓住這個原則會讓你不至於迷失方向。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 14:34:21

  上一篇:go  也談事件(Event)
  下一篇:go  基於CallContextInitializer的WCF擴展導致的嚴重問題