(轉自MSDN雜誌:http://msdn.microsoft.com/zh-cn/magazine/gg598927.aspx)javascript
在以前兩篇文章中,我介紹了使用 Microsoft Unity 2.0 進行的面向方面的編程 (AOP)。AOP 成型於二十世紀九十年代,是爲進一步改進和補充面向對象編程 (OOP) 而開發的編程技術,這種編程技術最近有所更新,而且獲得許多控制反轉 (IoC) 庫的支持。 Unity 也不例外。 AOP 的主要目的是讓開發人員更加有效地處理橫切關注點。 AOP 在本質上是解決了下面的問題:在爲某個應用程序設計對象模型的時候,您如何處理代碼的安全、緩存或日誌記錄等方面? 這些方面對於實現十分重要,但並不嚴格屬於您所構建的模型中的對象。 是否應在設計中大肆歸入業務以外的方面? 或者,利用其餘方面來修飾面向業務的類是否會更好? 若是您選擇後者,AOP 基本上能夠提供相關語法來定義和附加這些方面。java
所謂「方面」,就是橫切關注點的實現。 在方面的定義中,您須要指定一些事情。 首先,您須要爲所實現的關注點提供代碼。 在 AOP 術語中,這稱爲「建議」。 建議應用於某個特定的代碼點,該代碼點能夠是方法主體、屬性的 getter/setter,或者也多是異常處理程序。 這個代碼點稱爲「聯接點」。 最後,在 AOP 術語中,還有一個稱做「切入點」。 切入點是聯接點的集合。 一般,切入點經過方法名稱和通配符由條件進行定義。 最終,AOP 在運行時在聯接點先後注入建議代碼。 這樣一個建議即與一個切入點創建關聯。web
在以前的文章中,我討論了 Unity 的攔截 API。 藉助攔截 API 能夠定義附加到類上的建議。 在 Unity 術語中,建議是一個行爲對象。 一般,行爲附加到經過 Unity 的 IoC 機制進行解析的某個類型上,不過攔截機制並不嚴格要求使用 IoC 功能。 實際上,您能夠配置攔截,使之一樣應用於經過純代碼建立的實例。編程
行爲經過一個實現固定接口(IInterceptionBehavior 接口)的類來體現。 該接口有一個名爲 Invoke 的方法。經過重寫此方法,您實際上定義了要在常規方法調用以前和/或以後所執行的步驟。 除了配置腳本,您也可使用 Fluent 代碼將行爲附加到某個類型。 這樣,您所要作的只是定義一個聯接點。 那切入點呢?緩存
咱們上個月討論過,目標對象上全部被攔截的方法都依照行爲對象的 Invoke 方法中所表達的邏輯執行。 基本攔截 API 不能區分不一樣方法,而且不支持特定的匹配規則。 要作到這一點,您能夠求助於策略注入 API。安全
若是您用過 Microsoft Enterprise Library (EntLib) 最新版本 5.0 以前的版本,那麼您有可能據說過 Policy Injection Application Block (PIAB),而且您還可能在本身的某些應用程序中用過 PIAB。 EntLib 5.0 也有 PIAB 模塊。 那麼 Unity 策略注入與 EntLib PIAB 之間的區別何在?app
在 EntLib 5.0 中,PIAB 主要出於兼容方面的緣由而存在。 在新版本中,PIAB 程序集的內容發生了變化。 具體而言,攔截的全部功能組件如今都已成爲 Unity 的組成部分,而且先前 EntLib 版本中的全部系統提供的調用處理程序都已轉移至其餘程序集,如圖 1 所示。框架
圖 1 Microsoft Enterprise Library 5.0 中對調用處理程序的重構ide
調用處理程序 | Enterprise Library 5.0 中的新程序集 |
受權處理程序 | Microsoft.Practices.EnterpriseLibrary.Security.dll |
緩存處理的處理程序 | 從 PIAB 中刪除 |
異常處理的處理程序 | Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll |
日誌記錄處理程序 | Microsoft.Practices.EnterpriseLibrary.Logging.dll |
性能計數器處理程序 | Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll |
驗證處理程序 | Microsoft.Practices.EnterpriseLibrary.Validation.dll |
如圖 1 所示,每一個調用處理程序都已移相當聯應用程序塊的程序集。 所以,異常處理調用處理程序移至異常處理應用程序塊,而驗證處理程序移至驗證應用程序塊,依此類推。 其中惟一的例外是性能計數器處理程序,它移入了 PolicyInjection 程序集。 儘管程序集發生了變化,但類的命名空間仍然保持不變。 另外值得注意的是,出於安全方面的緣由,以前位於 PIAB 中的緩存調用處理程序已從 EntLib 5.0 中刪除,而只能從 EntLib Contrib CodePlex 網站得到:bit.ly/gIcP6H。 這些變化的影響是 PIAB 如今由舊的組件組成,這些組件只是爲了向後兼容而存在,而且仍需一些代碼變動才能與版本 5.0 兼容。 除非您對舊版本依賴嚴重,不然建議您升級策略注入層,以便利用已融入 Unity 應用程序塊的新的(且大致相似的)策略注入 API。 讓咱們深刻探討一下 Unity 中的策略注入。性能
策略注入指的是一層代碼,它擴展基本 Unity 攔截 API,以針對每一個方法添加映射規則和調用處理程序。 策略注入實現爲一種特別的攔截行爲,分兩個主要階段:初始化和執行時。
在初始化階段,框架首先肯定哪一個可用策略能夠應用於所攔截的目標方法。 在這裏,策略是一組操做,能夠按照特定順序注入到所攔截的對象與它的實際調用方之間。 您只能攔截針對策略注入顯式配置的對象上的方法(現有實例或新建的實例都可)。
肯定適用策略列表以後,策略注入框架開始準備操做管道(操做稱爲調用處理程序)。 管道經過組合爲每一個匹配策略而定義的全部處理程序而造成。 管道中的處理程序根據策略順序進行排序,而且父策略中的每一個處理程序都分配有優先級。 在調用某個啓用策略的方法時,將處理以前構建的管道。 若是該方法轉而調用同一對象上其餘啓用策略的方法,這些方法的處理程序管道將合併到主管道中。
調用處理程序比「行爲」更加具體,而且由於最初在 AOP 中定義而看起來與建議十分類似。 行爲應用於類型,由您負責爲不一樣的方法採起不一樣的操做,而調用處理程序則針對每一個方法進行指定。
調用處理程序造成於管道中,並按預先肯定的順序接受調用。 每一個處理程序可以訪問調用的詳細信息,包括方法名稱、參數、返回值和預期返回類型。 調用處理程序還能夠修改參數和返回值、中止調用在管道中的傳播以及引起異常。
值得注意的是,Unity 並不附帶提供任何調用處理程序。 您只能本身建立調用處理程序,或從 EntLib 5.0 引用應用程序塊並使用圖 1 中列出的任何調用處理程序。
調用處理程序是一個實現 ICallHandler 接口的類,例如:
Order 屬性表示此處理程序相對於全部其餘處理程序的優先級。 Invoke 方法返回一個包含該方法的任何返回值的類實例。
調用處理程序只是執行本身的具體工做,而後釋放管道,在這個意義上,調用處理程序的實現十分簡單。 爲了將控制權轉交給管道中的下一個處理程序,處理程序會調用從 Unity 運行時收到的 getNext 參數。getNext 參數是一個委託,定義爲:
而 InvokeHandlerDelegate 定義爲:
Unity 文檔提供了一個闡述攔截的清晰圖表。 在圖 2 中,您能夠看到一個略加修改的圖表,它表示了策略注入的體系結構。
圖 2 Unity 策略注入中的調用處理程序管道
在系統提供的策略注入行爲範圍以內,您能夠看處處理程序鏈用以處理對代理對象或派生類所調用的某個給定方法。 爲了完整概述 Unity 中的策略注入,咱們須要瞭解一下匹配規則。
經過匹配規則,您能夠指定在哪裏應用攔截邏輯。 若是您使用了行爲,您的代碼將會應用於整個對象;利用一條或更多匹配規則能夠定義篩選器。 匹配規則表示用以選擇特定對象和成員的條件,Unity 將爲這些對象和成員附加處理程序管道。 用 AOP 術語來講,匹配規則就是用以定義切入點的條件。 圖 3 列出了 Unity 提供本機支持的匹配規則。
圖 3 Unity 2.0 中受支持匹配規則的列表
匹配規則 | 說明 |
AssemblyMatchingRule | 基於指定程序集中的類型選擇目標對象。 |
CustomAttributeMatchingRule | 基於成員級別的自定義屬性選擇目標對象。 |
MemberNameMatchingRule | 基於成員名稱選擇目標對象。 |
MethodSignatureMatchingRule | 基於簽名選擇目標對象。 |
NamespaceMatchingRule | 基於命名空間選擇目標對象。 |
ParameterTypeMatchingRule | 基於某個成員的某個參數的類型名稱選擇目標對象。 |
PropertyMatchingRule | 基於成員名稱(包括通配符)選擇目標對象。 |
ReturnTypeMatchingRule | 基於返回類型選擇目標對象。 |
TagMatchingRule | 基於臨時 Tag 屬性的賦值選擇目標對象。 |
TypeMatchingRule | 基於類型名稱選擇目標對象。 |
匹配規則是實現 IMatchingRule 接口的類。 瞭解這一點以後,讓咱們來看看如何使用策略注入。 定義策略主要有三種方式:使用屬性,使用 Fluent 代碼,以及經過配置。
圖 4 是一個示例調用處理程序,它在某個操做獲得負值結果時引起異常。 我會在不一樣狀況下使用這個處理程序。
圖 4 NonNegativeCallHandler 類
使用處理程序最爲簡單的方式是在您所認爲它能夠發揮做用的位置將之附加至方法。 爲此,您須要一個屬性,例如:
下面是一個示例 Calculator 類,爲之附加了基於屬性的策略:
其結果是不論返回值是什麼,對於方法 Sum 的調用都會照常執行,而若有負數返回,對於方法 Sub 的調用則會引起異常。
若是您不喜歡屬性,也能夠經過 Fluent API 來表達相同的邏輯。 這種狀況下,在匹配規則方面須提供更多詳細信息。 咱們來看看如何表達這樣一種想法:只在返回 Int32 而且名爲 Sub 方法中注入代碼。 咱們使用 Fluent API 來配置 Unity 容器(參見圖 5)。
圖 5 用以定義一組匹配規則的 Fluent 代碼
請注意,若是您使用 ContainerControlledLifetimeManager 管理器,則全部方法一定會共享同一個調用處理程序實例。
這段代碼的效果是任何實現 ICalculator 的具體類型(即配置爲接受攔截並經過 Unity 進行解析)都會選擇兩個潛在的注入候選對象:Sub 方法和 Test 方法。 然而,只有返回類型爲 Int32 的方法纔會繼續匹配其餘匹配規則。 這就是說,若是 Test 返回雙精度值,它便會遭到排除。
最後,能夠經過配置文件表達一樣的理念。 圖 6 是 <unity> 節的預期內容。
圖 6 在配置文件中準備策略注入
結果代表,若是在一個策略中有多個匹配規則,最終結果是對全部規則應用布爾運算符 AND(也就是說,全都必須爲真)。 若是定義了多個策略,對於每一個策略都會進行獨立的匹配評估和處理程序應用。 於是,您能夠從不一樣的策略應用處理程序。
總結一下,攔截是 Microsoft .NET Framework 空間中大多數 IoC 框架實現面向方面編程的一種方式。 經過攔截,您能夠在任何特定程序集中任何特定類型的任何特定方法先後運行本身的代碼。 之前,EntLib 提供了特定的應用程序塊 PIAB 來執行這一工做。 在 EntLib 5.0 中,PIAB 的底層引擎已經轉入 Unity,而且實現爲 Unity 低級攔截 API 的一種特別行爲(對於這種 API 在以前兩期專欄中已有討論)。 策略注入行爲要求使用 Unity 容器,而且不只限於經過低級攔截 API 使用。
然而,低級攔截 API 不能用以選擇您要攔截的類型成員,您必須本身編寫代碼來執行這一工做。 但利用策略注入行爲,您能夠將精力集中於所需行爲的細節上面,而讓庫根據您所提供的規則來肯定行爲應用於哪些方法。