Dora.Interception,為.NET Core度身打造的AOP框架 [2]:以約定的方式定義攔截器
上一篇《更加簡練的程式設計體驗》提供了最新版本的Dora.Interception程式碼的AOP程式設計體驗,接下來我們會這AOP框架的程式設計模式進行詳細介紹,本篇文章著重關注的是攔截器的定義。採用“ 基於約定 ”的Interceptor定義方式是Dora.Interception區別於其他AOP框架的一個顯著特徵,要了解攔截器的程式設計約定,就得先來了解一下Dora.Interception中針對方法呼叫的攔截是如何實現的。
一、針對例項的攔截
總地來說,Dora.Interception針對方法呼叫的攔截機制分為兩種型別,我將它稱為“ 針對例項的攔截 ”和“ 針對型別 ”的攔截。針對例項的攔截應用於針對介面的方法呼叫,其原理如下所示:型別Foobar實現了介面IFoobar,如果需要攔截以介面的方式呼叫Foobar物件的某個方法,我們可以動態生成另一個用來封裝Foobar物件的FoobarProxy型別,FoobarProxy同樣實現IFoobar介面,我們在實現的方法中實現對Interceptor鏈的呼叫。我們最終將原始提供的Foobar物件封裝成FoobarProxy物件,那麼針對Foobar的方法呼叫將轉換成針對FoobarProxy物件的呼叫,攔截得以實現。
二、針對型別的攔截
如果Foobar並未實現任何介面,或者針對它的呼叫並非以介面的方式進行,那麼我們只能採用“針對型別的攔截”,其原理如下:我們動態建立Foobar的派生型別FoobarProxy,並重寫其需要被攔截的虛方法來實現對Interceptor鏈的呼叫。我們最終建立FoobarProxy物件來替換掉原始的Foobar物件,那麼針對Foobar的方法呼叫將轉換成針對FoobarProxy物件的呼叫,攔截得以實現。
由於這種攔截方式會直接建立代理物件,無法實現針對目標物件的封裝,當我們進行DI服務註冊的時候,只能指定註冊服務的實現型別,不能指定一個現有的Singleton例項或者提供一個建立例項的Factory。
三、從兩個Delegate說起
要理解Dora.Interception的設計,先得從如下這兩個特殊的Delegate型別( InterceptDelegate 和 InterceptorDelegate )說起。InterceptDelegate代表針對方法的攔截操作,作為輸入引數的InvocationContext提供了當前方法呼叫的所有上下文資訊,返回型別被設定為Task意味著Dora.Interception提供了針對基於Task的非同步程式設計的支援。
public delegate Task InterceptDelegate(InvocationContext context); public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next); public abstract class InvocationContext { public abstract object[] Arguments { get; } public abstract MethodBase Method { get; } public InterceptDelegate Next { get;} public abstract object Proxy { get; } public abstract object ReturnValue { get; set; } public abstract object Target { get; } public MethodBase TargetMethod { get; } public abstract IDictionary<string, object> ExtendedProperties { get; } public Task ProceedAsync(); }
InterceptDelegate表示的是“攔截操作”,即表示作用於InvocationContext上下文上的一個Task,但它並不能表示一個攔截器物件。原因很簡單,因為註冊到同一個方法上的多個攔截器物件會構成一個鏈條,最終決定是否呼叫後一個攔截器或者目標方法(對於鏈條尾部的Interceptor)是由當前攔截器決定的,所以如果將Interceptor也表示成委託物件,它的輸入應該是一個InterceptDelegate物件,表示針對後一個攔截器或者目標方法的呼叫,它返回的同樣也是一個InterceptDelegate物件,表示將自身納入攔截器鏈之後,新的攔截器鏈條(包括呼叫目標方法)所執行的操作。
所以一個Interceptor在Dora.Interception中應該表示成一個Func<InterceptDelegate, InterceptDelegate>物件,這與ASP.NET Core的中介軟體管道其實是一回事。簡單起見,我們為它專門定義了一個委託型別InterceptorDelegate。
四、將一個物件轉換成Interceptor
雖然Dora.Interception總是將Interceptor物件表示成上面介紹的InterceptorDelegate型別的委託,但是為了更好的程式設計體驗,我們可以選擇採用POCO型別的方法來定義Interceptor。為了提供更好的靈活性,能夠在方法中動態注入任意依賴服務,我們並不打算為這樣的Interceptor型別定義一個介面。介面是一個契約,同時也是一個 限制 。如果型別實現某個介面,意味著必需按照規定的宣告實現其方法,針對方法的服務注入將無法實現,所以Dora.Interception採用“基於約定”的方式來定義Interceptor型別。具體的約定如下
- Interceptor只需要定義一個普通的 例項型別 即可。
- Interceptor型別必須具有一個 公共建構函式 ,它可以包含任意的引數,並支援 構造器注入 。
- 攔截功能實現在約定的 InvokeAsync 的方法中,這是一個返回型別為 Task 的非同步方法,它的第一個引數型別為 InvocationContext 。
- 除了表示當前執行上下文的引數之外, 任何可以注入的服務於物件都可以定義成InvokeAsync方法的引數。
- 當前Interceptor針對後續的Interceptor或者目標方法的呼叫通過呼叫InvocationContext的 ProceedAsync 方法來實現。
如下所示的就是一個典型的Interceptor,它提供了針對建構函式和方法的注入。
public class FoobarInterceptor { public IFoo Foo { get; } public string Baz { get; } public FoobarInterceptor(IFoo foo, string baz) { Foo = foo; Baz = baz; } public async Task InvokeAsync(InvocationContext context, IBar bar) { await Foo.DoSomethingAsync(); await bar.DoSomethingAsync(); await context.ProceedAsync(); } }