相較於社區其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的編程方式。我們並沒有為Interceptor定義一個介面,正是因為不需要實現一個預定義的介面,Dora.Interception下的Interceptor定義變得更加自由。除此之外,Interc... ...
相較於社區其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的編程方式。我們並沒有為Interceptor定義一個介面,正是因為不需要實現一個預定義的介面,Dora.Interception下的Interceptor定義變得更加自由。除此之外,Interceptor的非同步執行是我在設計Dora.Interception之初最為關心的問題,也就是說如果Interceptor應用的目標方法是非同步的,Interceptor自身也應該被賦予非同步執行的能力。接下來我們就來聊聊如果你使用了Dora.Interception,如何定義你的Interceptor。
目錄
一、兩種代理類型生成方式
二、InterceptorDelegate
三、定義Interceptor類型
四、支持方法註入
一、兩種代理類型生成方式
Dora.Interception採用動態生成代理類型(採用IL Emit)的方式來實現針對目標方法的攔截,具體來說我們提供了兩種類型的代理類型生成方式:
- 如果目標類型實現了某個介面,我們會讓生成的代理類型實現這個介面,在代理類型中實現的介面方法會執行攔截操作,這意味著定義在介面中的所有方法都是可以被攔截的。代理對象會封裝目標對象,如果需要最終調用目標方法,被封裝的這個目標對象相應的方法會被調用。我們將這種形式的代理類型生成方式成為“基於介面的代碼生成”。
- 如果目標類型沒有實現介面,那麼生成的代理類型會直接派生於這個類型,如果定義在基類中的某個虛方法需要被攔截,我們會在代理類中通過重寫該方法來執行攔截操作。針對目標方法的調用可以通過調用基類對應的方法來實現。我們將這種形式的代理類型生成方式成為“基於虛方法的代碼生成”。
二、InterceptorDelegate
AOP的核心在於將一些非業務的功能定義成相應的Interceptor,並以橫切(Crosscutting)的形式註入到針對目標方法的調用過程中。換句話說,Interceptor需要攔截目標方法的執行,併在針對當前執行方法的上下文中執行被註入的操作。如果我們將方法執行的上下文定義成一個InvocationContext,那麼Interceptor需要執行的攔截操作就可以表示成一個Action<InvocationContext>。但是我們在上面說過了,一個Interceptor應該被賦予非同步執行的能力,按照基於Task的並行編程模式,Interceptor自身執行的攔截操作應該表示成一個Func<InvocationContext, Task>。
我們在Dora.Interception定義瞭如下這個抽象的InvocationContext來表示需要被攔截的方法執行上下文。它的Method和TargetMethod返回代表當前方法的MethodBase對象,如果採用基於介面的代理類型生成方式,前者表示定義在介面上的方法,後者則表示定義在目標類型上的方法。如果採用基於虛方法的代理類型生成方式,兩個屬性返回的是同一個對象,表示定義在被攔截類型中的方法。至於Proxy和Target則很明顯,分別表示當前的代理對象和被封裝的目標對象,如果採用基於虛方法的代理類型生成方式,兩個屬性返回同一個對象。
1 public abstract class InvocationContext 2 { 3 public abstract MethodBase Method { get; } 4 public MethodBase TargetMethod { get; } 5 public abstract object Proxy { get; } 6 public abstract object Target { get; } 7 public abstract object[] Arguments { get; } 8 public abstract object ReturnValue { get; set; } 9 }
InvocationContext的Arguments表示當前方法調用的參數,既包括一般的輸入參數,也包括ref/out參數。值得一提的是,Arguments屬性是可讀可寫的,也就說Interceptor可以通過修改該屬性中某個元素的值進而實現修改某個參數值的目的。由於整個調用過程共用同一個InvocationContext對象,所以某個Interceptor對Arguments所作的任何修改都將影響到後續Intercecptor以及最終目標方法的執行。InvocationContext的ReturnValue表示方法調用的返回值。如果目標方法最終被調用,它的返回值將最終反映在這個屬性上。這個屬性是可讀可寫的,任意Interceptor都可以通過修改這個屬性得到改變方法調用返回值的目的。
由於當前方法調用的執行上下文被表示成一個InvocationContext對象,所以實現在Interceptor上的攔截操作可以表示成一個Func<InvocationContext, Task>類型的委托。不過為了編程方便,我們專門定義瞭如下這個對應的委托類型InterceptDelegate。由於一個方法上可以同時應用多個Interceptor,那麼對應一個Interceptor在完成了自身定義的攔截操作之後,它還將決定是否繼續調用後續的Interceptor或者目標方法,或者說針對後續Interceptor或者目標方法的調用也屬於當前攔截操作的一部分,所以我們定義了另一個委托類型InterceptorDelegate來表示一個Interceptor對象。
1 public delegate Task InterceptDelegate(InvocationContext context); 2 public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);
對於表示Interceptor的InterceptorDelegate委托,它的輸入和輸出都是InterceptDelegate委托,Interceptor通過作為輸入的InterceptDelegate委托實現針對後續Interceptor或者目標方法的調用,它返回的InterceptDelegate委托對象則體現實現的攔截操作。從這個意義上講,一個InterceptorDelegate委托不僅僅表示一個單一的Interceptor對象,也可以表示由多一個Interceptor組成的Interceptor Chain。從另一個角度講,由於一個Interceptor已經實現了針對後續Interceptor的執行,所以一個Interceptor本身就表示一個Interceptor Chain。
三、定義Interceptor類型
雖然Dora.Interception在底層總是使用一個InterceptorDelegate委托表示Interceptor(Chain),為了編程上的便利,我們依然將Interceptor定義成一個類型,我們定義的Interceptor類型只需要採用如下的“約定”即可:
- Interceptor類型無需實現任何的介面,我們只需要定義一個普通的公共實例類型即可。
- Interceptor類型必須具有一個公共構造函數,並且該構造函數的第一個參數的類型必須是InterceptDelegate,後者用於調用後續的Interceptor或者目標方法。
- 除了第一個參數之外,上述這個構造函數可以包含任意的參數定義。
- 攔截功能實現在約定的InvokeAsync的方法中,這是一個返回類型為Task的非同步方法,它的第一個參數類型為InvocationContext。
- 除了表示當前執行上下文的參數之外,InvokeAsync可以包含任意的參數定義,但是要求這些參數能夠以DI的方式來提供。
- 當前Interceptor是否調用後續的Interceptor或者目標方法,取決於你是否調用構造函數傳入的這個InterceptDelegate委托對象。
接下來我們就通過實例演示的方式來簡單介紹一下如何遵循上述的這些約定來定義我們的Interceptor類型。如下麵的代碼片段所示,作為Interceptor類型的FoobarInterceptor具有一個公共的實例構造函數,作為強制要求的第一個參數next表示用於調用後續Interceptor或者目標方法的InterceptDelegate委托對象。除了該參數,我們還定義了額外兩個介面類型的參數,這些參數都被保存到對應的欄位或者屬性上。攔截操作定義在InvokeAsync方法,這個方法的方法名(InvokeAsync)、返回類型(Task)和第一個參數的類型(InvocationContext)都是我們約定的一部分。在這個方法中,我們輸出Foo和Bar屬性,並最終利用構造函數指定的InterceptDelegate委托對象將調用向後傳遞。
1 public class FoobarInterceptor 2 { 3 public IFoo Foo { get; } 4 public IBar Bar { get; } 5 private InterceptDelegate _next; 6 public FoobarInterceptor(InterceptDelegate next, IFoo foo, IBar bar) 7 { 8 _next = next; 9 this.Foo = foo; 10 this.Bar = bar; 11 } 12 13 public Task InvokeAsync(InvocationContext context) 14 { 15 Console.WriteLine(this.Foo); 16 Console.WriteLine(this.Bar); 17 return _next(context); 18 } 19 } 20 21 22 public class FoobarAttribute : InterceptorAttribute 23 { 24 public override void Use(IInterceptorChainBuilder builder) 25 { 26 builder.Use<FoobarInterceptor>(this.Order); 27 } 28 } 29 30 public interface IFoo { } 31 public interface IBar { } 32 public class Foo : IFoo { } 33 public class Bar : IBar { }
FoobarAttribute是用於註冊FoobarInterceptor的特性,FoobarAttribute派生於InterceptorAttribute這個抽象基類,關於InterceptorAttribute以及相關Interceptor註冊的類型我們將在後續的文章中進行介紹。Dora.Interception的一個顯著的特征就是與.NET Core的DI實現了無縫集成,具體體現在Interceptor中所需的任何服務都可以直接採用DI的方式來提供,比如FoobarInterceptor的Foo和Bar屬性對應的服務實例。如下麵的代碼片段所示,我們將FoobarAttribute標註到Demo類型的虛方法Invoke上。在Main方法中,我們將IFoo、IBar和Demo對應的服務註冊添加到創建的ServiceCollection上,然後調用後者的BuildInterceptableServiceProvider方法創建一個具有“攔截”特性的ServiceProvider。如果由這個ServiceProvider提供的服務類型能夠被攔截,它會利用相應的代理類型生成機制動態地生成對應的代理類型,並最終創建出對應的代理實例。
1 class Program 2 { 3 static void Main() 4 { 5 var demo = new ServiceCollection() 6 .AddScoped<IFoo, Foo>() 7 .AddScoped<IBar, Bar>() 8 .AddScoped<Demo, Demo>() 9 .BuildInterceptableServiceProvider() 10 .GetRequiredService<Demo>(); 11 demo.Invoke(); 12 } 13 } 14 15 public class Demo 16 { 17 [Foobar] 18 public virtual void Invoke() 19 { 20 Console.WriteLine("Demo.Invoke() is invoked"); 21 } 22 }
執行上面的代碼會在控制臺上產生如下的輸出結果,我們可以看出應用在Domo.Invoke方法上的FoobarInteceptor被正常執行,它依賴的兩個服務類型Foo和Bar正好與服務註冊一致。
四、支持方法註入
對於面定義的FoobarInteceptor來說,它依賴的兩個服務Foo和Bar實際上是通過構造器註入的方式提供的,實際上我們還具有更加簡潔的方案,那就是直接在InvokeAsync方法中對它們進行註入,這也是我們為什麼不為Interceptor定義介面的原因。直接採用方法註入會讓你的Interceptor變得更加簡潔。
1 public class FoobarInterceptor 2 { 3 private InterceptDelegate _next; 4 public FoobarInterceptor(InterceptDelegate next) 5 { 6 _next = next; 7 } 8 9 public Task InvokeAsync(InvocationContext context, IFoo foo, IBar bar) 10 { 11 Console.WriteLine(foo); 12 Console.WriteLine(bar); 13 return _next(context); 14 } 15 }
Dora.Interception, 為.NET Core度身打造的AOP框架 [1]:全新的版本
Dora.Interception, 為.NET Core度身打造的AOP框架 [2]:不一樣的Interceptor定義方式
Dora.Interception, 為.NET Core度身打造的AOP框架 [3]:Interceptor的註冊