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

來源:互聯網
上載者:User

轉自:http://www.cnblogs.com/artech/archive/2010/07/13/1776818.html

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的管道

作為一個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的具體實現原理的時候,抓住這個原則會讓你不至於迷失方向。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.