全新升級的AOP框架Dora.Interception[3]: 基於特性標註的攔截器註冊方式
- 2022 年 6 月 22 日
- 筆記
- .NET, .NET Core, [05] 開源框架, AOP, Dependency Injection, Dora, Dora.Interception, Roslyn
在Dora.Interception(github地址,覺得不錯不妨給一顆星)中按照約定方式定義的攔截器可以採用多種方式註冊到目標方法上。本篇文章介紹最常用的基於「特性標註」的攔截器註冊方式,下一篇會介紹另一種基於(Lambda)表達式的註冊方式。如果原生定義的這兩種註冊方式不能滿足要求,利用框架提供的擴展,我們可以完成任何你想要的攔截器註冊手段。(拙著《ASP.NET Core 6框架揭秘》於日前上市,加入讀者群享6折優惠)
目錄
一、InterceptorAttribute 特性
二、指定構造攔截器的參數列表
三、將攔截器類型定義成特性
四、合法性檢驗
五、針對類型、屬性的標註
六、攔截的屏蔽
一、InterceptorAttribute 特性
攔截器類型可以利用如下這個InterceptorAttribute特性應用到標註的類型、屬性和方法上。除了通過Interceptor屬性指定攔截器類型之外,我們還可以利用Order屬性控制攔截器的執行順序,該屬性默認值為0。該特性的Arguments用來提供構造攔截器對象的參數。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true, Inherited = false)] public class InterceptorAttribute : Attribute { public Type Interceptor { get; } public object[] Arguments { get; } public int Order { get; set; } public InterceptorAttribute(params object[] arguments) : public InterceptorAttribute(Type? interceptor, params object[] arguments); }
二、指定構造攔截器的參數列表
攔截器對象是通過依賴注入容器提供的,容器能夠自動提供注入到構造函數中對象。如果構造函數包含額外的參數,對應的參數值就需要利用InterceptorAttribute 特性的Arguments屬性來提供,此屬性由構造函數的arguments參數提供。
public class FoobarInterceptor { public string Name { get; } public FoobarInterceptor(string name, IFoobar foobar) { Name = name; Debug.Assert(foobar is not null); } public ValueTask InvokeAsync(InvocationContext invocationContext) { Console.WriteLine($"FoobarInterceptor '{Name}' is invoked."); return invocationContext.ProceedAsync(); } } public interface IFoobar { } public class Foobar : IFoobar { }
對於如上這個攔截器類型FoobarInterceptor,其構造函數定義了一個字符串的參數name用來指定攔截器的名稱,當我利用InterceptorAttribute 特性將此攔截器應用到Invoker類型的Invoke1和Invoke2方法上是,就需要按照如下的方式指定具體的名稱(Interceptor1和Interceptor2)。
public class Invoker { [FoobarInterceptor("Interceptor1")] public virtual void Invoke1() => Console.WriteLine("Invoker.Invoke1()"); [FoobarInterceptor("Interceptor2")] public virtual void Invoke2() => Console.WriteLine("Invoker.Invoke2()"); }
我們按照如下的方式調用Invoker對象的Invoke1和Invoke2方法。
var invoker = new ServiceCollection()
.AddSingleton<Invoker>()
.AddSingleton<IFoobar, Foobar>()
.BuildInterceptableServiceProvider()
.GetRequiredService<Invoker>();
invoker.Invoke1();
invoker.Invoke2();
程序執行後,攔截器會以如下的形式將自身的名稱輸出到控制台上(源代碼)。
三、將攔截器類型定義成特性
其實我們可以讓定義的攔截器類型派生於InterceptorAttribute 特性,這樣就可以直接將它標註到目標類型、屬性和方法上。比如上面這個FoobarInterceptor類型可以改寫成如下的形式。
public class FoobarInterceptorAttribute: InterceptorAttribute { public string Name { get; } public FoobarInterceptorAttribute(string name) => Name = name; public ValueTask InvokeAsync(InvocationContext invocationContext) { Console.WriteLine($"FoobarInterceptor '{Name}' is invoked."); return invocationContext.ProceedAsync(); } }
那麼它就可以按照如下的方式標註到Invoker類型的兩個方法上(源代碼)。
public class Invoker { [FoobarInterceptor("Interceptor1")] public virtual void Invoke1() => Console.WriteLine("Invoker.Invoke1()"); [FoobarInterceptor("Interceptor2")] public virtual void Invoke2() => Console.WriteLine("Invoker.Invoke2()"); }
四、合法性檢驗
只有接口方法和虛方法才能被攔截,Dora.Interception針對攔截器的應用提供了如下的驗證邏輯:
- 標註到方法上(函數屬性的Get/Set方法):如果目標方法均不能被攔截,拋出異常;
- 標註到屬性上:表示將攔截器應用到該屬性可以被攔截的Get/Set方法上。如果Get和Set方法均不能被攔截,拋出異常;
- 標註到類型上:表示將攔截器應用到目標類型可以來攔截的方法(含屬性方法)上,如果類型的所有方法均不能被攔截,此時不會拋出異常。
public class Foo { [FoobarInterceptor] public void M() { } } public class Bar { [FoobarInterceptor] public object? P { get; set; } } [FoobarInterceptor] public class Baz { public void M() { } }
對於上面定義的三個類型,Foo的M方法和Bar的P屬性均是無法被攔截,Baz類型並沒有可以被攔截的方法。我們採用如下的程序測試上述的檢驗邏輯。
GetService<Foo>(); GetService<Bar>(); GetService<Baz>(); static void GetService<T>() where T:class { try { Console.WriteLine($"{typeof(T).Name}:"); _ = new ServiceCollection() .AddSingleton<T>() .BuildInterceptableServiceProvider() .GetRequiredService<T>(); Console.WriteLine("OK"); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
程序運行後會在控制台上輸出如下的結果,可以看出只有將攔截器應用到不合法的方法和屬性上才會拋出異常(源代碼)。
五、針對類型、屬性的標註
我們利用如下這個攔截器類型FoobarInterceptorAttribute 來演示將攔截器應用到類型和屬性上。該攔截器類型派生於InterceptorAttribute特性,並在執行的時候輸出當前的方法。
public class FoobarInterceptorAttribute : InterceptorAttribute { public ValueTask InvokeAsync(InvocationContext invocationContext) { var method = invocationContext.MethodInfo; Console.WriteLine($"{method.DeclaringType!.Name}.{method.Name} is intercepted."); return invocationContext.ProceedAsync(); } }
我們將FoobarInterceptorAttribute 特性標註到Foo類型上,後者定義的M1方法和P1屬性是可以被攔截的,但是M2方法和P2屬性則不能。FoobarInterceptorAttribute 特性還被應用到Bar類型的P1屬性以及P2屬性的Set方法上。
[FoobarInterceptor] public class Foo { public virtual void M1() { } public void M2() { } public virtual object? P1 { get; set; } public object? P2 { get; set; } } public class Bar { [FoobarInterceptor] public virtual object? P1 { get; set; } public virtual object? P2 { get; [FoobarInterceptor] set; } }
我們利用如下的程序來檢驗針對Foo和Bar對象所有方法和屬性的調用,那麼被攔截器攔截下來。
var provider = new ServiceCollection() .AddSingleton<Foo>() .AddSingleton<Bar>() .BuildInterceptableServiceProvider(); var foo = provider.GetRequiredService<Foo>(); var bar = provider.GetRequiredService<Bar>(); foo.M1(); foo.M2(); foo.P1 = null; _ = foo.P1; foo.P2 = null; _ = foo.P2; Console.WriteLine(); bar.P1 = null; _ = bar.P1; bar.P2 = null; _ = bar.P2;
程序運行之後會在控制台上輸出如下的結果(源代碼)。
六、攔截的屏蔽
如果某個攔截器需要被應用大某個類型的絕大部分成員,我們可以選擇「排除法」:將攔截器應用到該類型上,將某些非目標成員屏蔽掉。還有一種情況下,如果我們確定某些類型或者方法不能被攔截(比如會在一個循環中頻繁調用),又擔心一些「模糊」的攔截器註冊方法將它們與某些攔截器錯誤地關聯在一起,此時我們可以選擇將其攔截功能顯式屏蔽掉。
針對攔截的屏蔽可以通過在類型、屬性、方法設置程序集上標註NonInterceptableAttribute特性。由於屏蔽功能具有最高優先級,一旦將此特性應用到某個類型上,該類型上的所有成員均不會被攔截。如果被標註到屬性上,其Get和Set方法也不會被攔截。具有如下定義的Foo和Bar類型的所有方法和屬性都不會被攔截(源代碼)。
[FoobarInterceptor] public class Foo { [NonInterceptable] public virtual void M() { } [NonInterceptable] public virtual object? P1 { get; set; } public virtual object? P2 { [NonInterceptable] get; set; } } [NonInterceptable] public class Bar { [FoobarInterceptor] public virtual void M() { } [FoobarInterceptor] public virtual object? P { get; set; } }
全新升級的AOP框架Dora.Interception[1]: 編程體驗
全新升級的AOP框架Dora.Interception[2]: 基於約定的攔截器定義方式
全新升級的AOP框架Dora.Interception[3]: 基於「特性標註」的攔截器註冊方式
全新升級的AOP框架Dora.Interception[4]: 基於「Lambda表達式」的攔截器註冊方式
全新升級的AOP框架Dora.Interception[5]: 實現任意的攔截器註冊方式
全新升級的AOP框架Dora.Interception[6]: 框架設計和實現原理