面向方面的編程、偵聽和 Unity 2.0(轉)

(轉自msdn雜誌:http://msdn.microsoft.com/zh-cn/magazine/gg490353.aspxjavascript

毫無疑問,面向對象是一種主流編程模式,當涉及到將某個系統分割爲組件並經過組件來描述過程時,這種模式佔有優點。 當處理某組件的業務特定關注點時,面向對象 (OO) 模式一樣佔有優點。可是,當涉及處處理橫切關注點時,OO 模式再也不有效。 通常來講,橫切關注點是一個在系統中影響多個組件的關注點。java

爲了最大限度地重用複雜的業務邏輯代碼,您一般傾向於圍繞系統的核心和主要業務功能設計類的層次結構。 但其餘橫切類層次結構的非業務特定關注點該如何實現? 緩存、安全和日誌記錄等功能在什麼位置適合? 極可能就是在每一個受影響的對象中重複使用這些功能。web

橫切關注點是必須在一個不一樣的邏輯級別(超出應用程序類範圍的級別)處理的系統的一個方面,而不是給定組件或系列組件的特定職責。 出於此緣由,多年前就定義了一個不一樣的編程模式:面向方面的編程 (AOP)。 順便說一下,AOP 這一律念於 20 世紀 90 年代在 Xerox PARC 實驗室中產生。 該團隊還開發出第一種 AOP 語言(還是最受歡迎的):AspectJ。編程

儘管幾乎全部人都認同 AOP 的好處,但它仍未普遍實現。 在我看來,這種應用範圍有限的主要緣由基本上是缺少合適的工具。 我深信,Microsoft .NET Framework 本機支持 AOP(即便只是部分支持)的那一天將成爲 AOP 的歷史轉折點。 如今,您只能使用 ad hoc 框架在 .NET 中實現 AOP。設計模式

.NET 中 AOP 的最強大工具是 PostSharp,您可在 sharpcrafters.com 中找到。 PostSharp 提供一個完整的 AOP 框架,您可在該框架中體驗 AOP 理論的全部關鍵功能。 但應注意,許多依賴關係注入 (DI) 框架都包括一些 AOP 功能。緩存

例如,您會在 Spring.NET、Castle Windsor 固然還有 Microsoft Unity 中發現 AOP 功能。 對於相對簡單的方案(例如,在應用層跟蹤、緩存和修飾組件),DI 框架的功能一般能成功應用。 但對於域對象和 UI 對象,很難使用 DI 框架得到成功。 無疑,橫切關注點會被視爲外部依賴關係,而 DI 技術也一定容許您在類中注入外部依賴關係。安全

關鍵在於,DI 極可能將要求進行 ad hoc 前期設計或稍作重構。 換句話說,若是您已在使用 DI 框架,則很容易就能導入一些 AOP 功能。 反之,若是您的系統未使用 DI,則導入 DI 框架可能須要至關多的工做。 這在大型項目中或在更新舊系統的過程當中並不老是可能實現的。 經過改用典型的 AOP 方法,可在一個稱爲「方面」的新組件中包裝全部橫切關注點。 在本文中,首先我將向您快速概述一下面向方面的模式,而後介紹您在 Unity 2.0 中發現的 AOP 相關功能。架構

AOP 快速指南

面向對象的編程 (OOP) 項目由多個源文件組成,每一個源文件實現一個或多個類。 該項目還包括表示橫切關注點(如日誌記錄或緩存)的類。 全部類均由編譯器處理並生成可執行代碼。 在 AOP 中,一個方面表示一個可重用的組件,它將多個類所需的行爲封裝在項目中。 實際處理方面的方式取決於您所考慮的 AOP 技術。 一般狀況下,咱們能夠說各個方面並不簡單直接地由編譯器進行處理。 若要修改可執行代碼以將方面考慮在內,須要一種額外的特定於技術的工具。 讓咱們大體看一下使用 AspectJ(第一個建立的 AOP 工具,即 Java AOP 編譯器)會發生什麼狀況。框架

藉助 AspectJ,您可以使用 Java 編程語言來編寫您的類,並使用 AspectJ 語言來編寫方面。 AspectJ 支持自定義語法,您可經過自定義語法指示方面的預期行爲。 例如,日誌記錄方面可能指定它將在調用特定方法以前和以後記錄。 各個方面以某種方式合併到常規源代碼中併產生源代碼的中間版本,而後將該中間版本編譯成可執行格式。 在 AspectJ 術語中,預處理方面並將方面與源代碼合併的組件稱爲 weaver 該組件產生一個編譯器可呈現給可執行文件的輸出。編程語言

總之,一個方面描述一段可重用的代碼,您但願將可重用代碼注入現有類中,而不接觸這些類的源代碼。 在其餘 AOP 框架(如 .NET PostSharp 框架)中,您將找不到 weaver 工具。 可是,方面的內容始終由框架進行處理並生成某種形式的代碼注入。

請注意,在這方面上,代碼 注入不一樣於依賴關係 注入。 代碼注入是指,AOP 框架可以將對方面中特定點處的公共終結點的調用插入到使用給定方面修飾的類主體中。 舉例來講,PostSharp 框架讓您可以將方面編寫爲 .NET 屬性,而後將這些屬性附加到類中的方法上。 PostSharp 屬性由 PostSharp 編譯器(咱們甚至能夠稱之爲 weaver)在生成後步驟中進行處理。 實際效果是,您的代碼獲得加強,從而在這些屬性中包括一些代碼。 但注入點將獲得自動解析,您做爲一名開發人員只需編寫一個獨立方面組件並將其附加到公共類方法便可。 代碼易於編寫,甚至更易於維護。

爲了完成這次有關 AOP 的快速概述,我將介紹一些特定術語並解釋它們各自的含義。 聯接點 指示您要在目標類的源代碼中注入方面代碼的點。 pointcut 表示聯接點集合。 建議 指的是要在目標類中注入的代碼。 可在聯接點的先後和四周注入代碼。 一個建議與一個 pointcut 關聯。 這些術語來自 AOP 的原始定義,在您使用的特定 AOP 框架中可能不會反映在字面上。 建議您嘗試選取這些術語隱含的概念(AOP 的核心概念),而後使用這種知識更好地瞭解特定框架的詳細信息。

Unity 2.0 快速指南

Unity 是做爲 Microsoft Enterprise Library 項目的一部分提供的應用程序塊,它也可單獨下載。 Microsoft Enterprise Library 是應用程序塊的集合,該集合處理大量描述 .NET 應用程序開發特徵的橫切關注點,如日誌記錄、緩存、加密、異常處理等。 Enterprise Library 的最新版本是 5.0,於 2010 年 4 月份發佈,並附帶對 Visual Studio 2010 的徹底支持(在 msdn.microsoft.com/library/ff632023 上的「模式和實踐開發人員中心」處,可瞭解該版本的詳細信息)。

Unity 是 Enterprise Library 應用程序塊之一。 Unity 一樣適用於 Silverlight,它實質上是爲攔截機制提供額外支持的 DI 容器,經過攔截機制可以使您的類更加面向方面。

Unity 2.0 中的攔截功能

Unity 中攔截的核心理念是讓開發人員可以自定義調用鏈,方便對對象調用方法。 也就是說,Unity 攔截機制經過在方法的常規執行先後或四周額外添加一些代碼,捕獲對已配置對象進行的調用並自定義目標對象的行爲。 攔截其實是在運行時向對象中添加新行爲的一種極其靈活的方法,無需接觸到對象的源代碼,也不會影響相同繼承路徑中的類的行爲。 Unity 攔截是實現 Decorator 模式的一種方式,該模式是一種經常使用設計模式,設計爲在運行時擴展正在使用的對象的功能。 Decorator 是一個容器對象,它接收目標對象的實例(和維護對實例的引用),並向外界擴充其功能。

Unity 2.0 中的攔截機制同時支持實例攔截和類型攔截。 此外,無論實例化對象的方式如何,不管對象是經過 Unity 容器建立的仍是一個已知實例,攔截都照常工做。 在後一種狀況下,您只需使用一個不一樣的徹底獨立的 API 便可。 可是,若是您這麼作,則將丟失配置文件支持。 圖 1 演示 Unity 中攔截功能的體系結構,並詳細說明該功能在未經過容器解析的特定對象實例上的工做方式。 (此圖只是對 MSDN 文檔中的某幅圖稍作了一些修改。)

圖 1 Unity 2.0 中對象攔截的工做方式

攔截子系統由三個關鍵元素組成:偵聽器(或代理);行爲管道;以及行爲或方面。 這些子系統的兩個極端分別爲客戶端應用程序和目標對象(即,被分配了未在其源代碼中進行硬編碼的其餘行爲的對象)。 在將客戶端應用程序配置爲在給定實例上使用 Unity 的攔截 API 後,全部方法調用都將經過一個代理對象(偵聽器)。 此代理對象查看已註冊行爲的列表,並經過內部管道調用這些行爲。 每一個配置的行爲都有機會在對象方法的常規調用以前或以後運行。 該代理將輸入數據注入到管道中,而後在數據經目標對象最初生成接着由行爲進一步修改後,該代理接收任何返回值。

配置攔截

在 Unity 2.0 中建議使用攔截的方法不一樣於早期版本,儘管在早期版本中使用的方法徹底支持向後兼容性。在 Unity 2.0 中,攔截只是您添加到容器中的一個新擴展,用來描述對象的實際解析方式。 下面是您但願經過 Fluent 代碼配置攔截時所需的代碼:

  1.  
  2.           var container = new UnityContainer();
  3. container.AddNewExtension<Interception>();
  4.         

該容器須要查找有關要攔截的類型和要添加的行爲的信息。 可以使用 Fluent 代碼或經過配置添加此信息。 我發現配置特別靈活,由於您無需接觸應用程序也無需執行任何新的編譯步驟,便可修改一些內容。 讓咱們採用基於配置的方法。

首先,在配置文件中添加如下內容:

<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.
          Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration"/>

此腳本的目的是使用特定於攔截子系統的新元素和別名來擴展配置架構。 另外,添加如下內容:

<container> <extension type="Interception" /> <register type="IBankAccount" mapTo="BankAccount"> <interceptor type="InterfaceInterceptor" /> <interceptionBehavior type="TraceBehavior" /> </register> </container>

若要使用 Fluent 代碼實現相同的任務,您須要對容器對象調用 AddNewExtension<T> 和 RegisterType<T>。

讓咱們進一步看一下配置腳本。 <extension> 元素將攔截添加到容器中。 請注意,腳本中使用的「Interception」是在節擴展中定義的別名之一。 接口類型 IBankAccount 映射到具體類型 BankAccount(這是 DI 容器的典型做業),並與特定類型的偵聽器相關聯。 Unity 提供兩種主要類型的偵聽器:實例偵聽器和類型偵聽器。 下個月,我將深刻探討偵聽器。 如今,一句話說明,實例偵聽器建立一個代理來篩選針對已截獲實例傳入的調用。 相反,類型偵聽器只是模擬已截獲對象的類型,並在派生類型的實例上工做。 (有關偵聽器的詳細信息,請參閱 msdn.microsoft.com/library/ff660861(PandP.20)。)

接口偵聽器是僅限於充當對象上一個接口的代理的實例偵聽器。 接口偵聽器使用動態代碼生成來建立代理類。 配置中的攔截行爲元素指示您要圍繞已截獲對象實例運行的外部代碼。 必須經過聲明的方式配置 TraceBehavior 類,以便容器能夠解析該類及其任何依賴關係。 使用 <register> 元素告知容器 TraceBehavior 類及其所需的構造函數,以下所示:

<register type="TraceBehavior"> <constructor> <param name="source" dependencyName="interception" /> </constructor> </register>

圖 2 顯示 TraceBehavior 類中的一段摘錄。

圖 2 Unity 行爲示例

  1.  
  2.           class TraceBehavior : IInterceptionBehavior, IDisposable
  3. {
  4.   private TraceSource source;
  5.  
  6.   public TraceBehavior(TraceSource source)
  7.   {
  8.     if (source == null
  9.       throw new ArgumentNullException("source");
  10.  
  11.     this.source = source;
  12.   }
  13.    
  14.   public IEnumerable<Type> GetRequiredInterfaces()
  15.   {
  16.     return Type.EmptyTypes;
  17.   }
  18.  
  19.   public IMethodReturn Invoke(IMethodInvocation input, 
  20.     GetNextInterceptionBehaviorDelegate getNext)
  21.   {
  22.      // BEFORE the target method execution 
  23.      this.source.TraceInformation("Invoking {0}",
  24.        input.MethodBase.ToString());
  25.  
  26.      // Yield to the next module in the pipeline
  27.      var methodReturn = getNext().Invoke(input, getNext);
  28.  
  29.      // AFTER the target method execution 
  30.      if (methodReturn.Exception == null)
  31.      {
  32.        this.source.TraceInformation("Successfully finished {0}",
  33.          input.MethodBase.ToString());
  34.      }
  35.      else
  36.      {
  37.        this.source.TraceInformation(
  38.          "Finished {0} with exception {1}: {2}",
  39.          input.MethodBase.ToString(),
  40.          methodReturn.Exception.GetType().Name,
  41.          methodReturn.Exception.Message);
  42.      }
  43.  
  44.      this.source.Flush();
  45.      return methodReturn;
  46.    }
  47.  
  48.    public bool WillExecute
  49.    {
  50.      get { return true; }
  51.    }
  52.  
  53.    public void Dispose()
  54.    {
  55.      this.source.Close();
  56.    }
  57.  }
  58.         

行爲類實現 IinterceptionBehavior,它基本上由 Invoke 方法組成。 Invoke 方法包含您要用於受偵聽器控制的任何方法的整個邏輯。 若是您想要在調用目標方法以前執行一些操做,則在該方法開頭執行操做。 當您想要運行到目標對象(或者更準確的說是運行到管道中註冊的下一個行爲)時,需調用框架提供的 getNext 委派。 最後,您可以使用任何所需的代碼對目標對象進行後處理。 Invoke 方法須要返回對管道中下一個元素的引用;若是返回 Null,則鏈中斷,後續的行爲將永遠不會被調用。

配置靈活性

攔截(更籠統的說是 AOP)知足了許多有用的方案的要求。 例如,利用攔截,您可向各個對象中添加責任,而無需修改整個類,而且保持解決方案相對於使用 Decorator 來講更加靈活。

本文只涉及了應用於 .NET 的 AOP 的一些皮毛。 在接下來的幾個月裏,我將撰寫有關 Unity 和 AOP 中攔截的更多內容。

相關文章
相關標籤/搜索