對於所有的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]: 自定義攔截器注冊方式