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類型(InterceptDelegateInterceptorDelegate)說起。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();
    }
}

[1]:更加簡練的編程體驗 [2]:基於約定的攔截器定義方式 [3]:多樣性的攔截器應用方式 [4]:與依賴注入框架的深度整合 [5]:對攔截機制的靈活定製

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章