1. 程式人生 > >AOP框架Dora.Interception 3.0 [3]: 攔截器設計

AOP框架Dora.Interception 3.0 [3]: 攔截器設計

對於所有的AOP框架來說,多個攔截器最終會應用到某個方法上。這些攔截器按照指定的順序構成一個管道,管道的另一端就是針對目標方法的呼叫。從設計角度來將,攔截器和中介軟體本質是一樣的,那麼我們可以按照類似的模式來設計攔截器。

一、InvocationContext

我們為整個攔截器管道定義了一個統一的執行上下文,並將其命名為InvocationContext。如下面的程式碼片段所示,我們可以利用InvocationContext物件得到方法呼叫上下文的相關資訊,其中包括兩個方法(定義在介面和實現型別),目標物件、引數列表(含輸入和輸出引數)、返回值(可讀寫)。Properties 屬性提供了一個自定義的屬性容器,我們可以利用它來存放任意與當前方法呼叫上下文相關的資訊。如果需要呼叫後續的攔截器或者目標方法(如果當前為最後一個攔截器),我們只需要直接呼叫ProceedAsync方法即可。

public abstract class InvocationContext
{    
    public abstract MethodInfo Method { get; }
    public MethodInfo TargetMethod { get; }
    public abstract object Target { get; }
    public abstract object[] Arguments { get; }
    public abstract object ReturnValue { get; set; }  
    public abstract IDictionary<string, object> Properties { get; }
    public Task ProceedAsync();
}

二、兩個委託物件

既然所有的攔截器都是在同一個InvocationContext上下文中執行的,那麼我們可以將任意的攔截操作定義成一個Func<InvocationContext, Task>物件。Func<InvocationContext, Task>物件不僅可以表示某項單一的攔截操作,實際上包括目標方法呼叫在內的整個攔截器管道都可以表示成一個Func<InvocationContext, Task>物件。由於這個委託的重要性,我們將它定義成如下這個InterceptDelegate型別。

public delegate Task InterceptDelegate(InvocationContext context);

如果以ASP.NET Core框架的請求處理管道作為類比,那麼InvocationContext相當於HttpContext,而InterceptDelegate自然對應的就是RequestDelegate。我們知道ASP.NET Core框架將中介軟體表示成Func<RequestDelegate, RequestDelegate>物件,那麼攔截器自然就可以表示成一個Func<InterceptDelegate, InterceptDelegate>。如果讀者朋友對此不太理解,可以參閱我的文章《200行程式碼,7個物件——讓你瞭解ASP.NET Core框架的本質》。由於攔截器的重要性,我們也將它定義成如下這個單獨的InterceptorDelegate型別。

public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);

三、基於約定的攔截器定義

Dora.Interception和ASP.NET Core採用幾乎一致的設計。對於ASP.NET Core來說,雖然中介軟體最終是通過Func<InterceptDelegate, InterceptDelegate>表示的,但是我們可以將中介軟體定義成一個按照約定定義的型別。Dora.Interception同樣支援基於約定的攔截器型別定義。

public class FoobarInterceptor
{
    private readonly IFoo _foo;
    private readonly IBar _bar;
    private readonly string _baz;

    public FoobarInterceptor(IFoo foo, IBar bar, string baz)
    {
        _foo = foo;
        _bar = bar;
        _baz = baz;
    }

    public async InvokeAsync(InvocationContext context)
    {
        await PreInvokeAsync();
        await context.ProceedAsync();
        await PostInvokeAsync();
    }
}

如上定義的FoobarInterceptor展現了一個典型的基於約定定義的攔截器型別,它體現瞭如下的約定:

  • 攔截器型別是一個例項型別(不能定義成靜態型別);
  • 必須具有一個公共建構函式,其中可以定義任意引數。
  • 攔截操作定義在一個名為InvokeAsync的方法中,該方法的返回型別為Task,其中包含一個InvocationContext型別的引數。如果需要呼叫後續攔截器管道,需要顯式呼叫InvocationContext上下文的ProceedAsync方法。

四、兩種注入方式

由於攔截器最終是利用.NET Core的依賴注入框架提供的,所以依賴服務可以直接注入攔截器的建構函式中。但是就服務的生命週期來講,攔截器本質上是一個Singleton服務,我們不應該將Scoped服務注入到它的建構函式中。如果具有針對Scoped服務注入的需要,我們應該將它注入到InvokeAsync方法中。

public class FoobarInterceptor
{
    private readonly string _baz;

    public FoobarInterceptor(string baz)
    {
        _baz = baz;
    }

    public async InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
    {
        await PreInvokeAsync();
        await context.ProceedAsync();
        await PostInvokeAsync();
    }
}

當Dora.Interception在呼叫InvokeAsync方法的時候,它會利用當前Scope的IServiceProvider物件來提供其引數。對於ASP.NET Core應用來說,如果攔截器的執行在整個請求處理的呼叫鏈中,這個IServiceProvider物件就是當前HttpContext的RequestServices屬性。如果當前IServiceProvider不存在,作為根的IServiceProvider物件會被使用。

AOP框架Dora.Interception 3.0 [1]: 程式設計體驗
AOP框架Dora.Interception 3.0 [2]: 實現原理
AOP框架Dora.Interception 3.0 [3]: 攔截器設計
AOP框架Dora.Interception 3.0 [4]: 基於特性的攔截器註冊
AOP框架Dora.Interception 3.0 [5]: 基於策略的攔截器註冊
AOP框架Dora.Interception 3.0 [6]: 自定義攔截器註冊方式