Dora.Interception(github地址,覺得不錯不妨給一顆星)提供了兩種攔截器註冊方式,一種是利用標註在目標型別、屬性和方法上的InterceptorAttribute特性,另一種採用基於目標方法或者屬性的呼叫表示式。通過提供的擴充套件點,我們可以任何我們希望的攔截器註冊方式。(拙著《ASP.NET Core 6框架揭祕》6折優惠,首印送簽名專屬書籤)、
目錄
一、IInterceptorProvider
二、InterceptorProviderBase
三、實現一種「萬能」的攔截器註冊方式
四、ConditionalInterceptorProvider
攔截器最終需要應用到某個具體的目標方法上,所以攔截器的註冊就是如何建立攔截器與目標方法之間的對映關係,Dora.Interception將這一功能體現在如下所示的IInterceptorProvider介面上。顧名思義,IInterceptorProvider旨在解決為某個型別的某個方法提供攔截器列表的問題,這一個功能體現在GetInterceptors方法上。如下面的程式碼片段所示,該方法返回一組Sortable<InvokeDelegate>物件,InvokeDelegate代表攔截器本身,Sortable<InvokeDelegate>物件在此基礎上新增了必要排序元素。
public interface IInterceptorProvider { bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed); IEnumerable<Sortable<InvokeDelegate>> GetInterceptors(Type targetType, MethodInfo method); void Validate(Type targetType, Action<MethodInfo> methodValidator, Action<PropertyInfo> propertyValidator) {} } public sealed class Sortable<T> { public int Order { get; } public T Value { get; set; } public Sortable(int order, T value) { Order = order; Value = value; } }
除了GetInterceptors方法,IInterceptorProvider介面還定義了額外兩個方法,CanIntercept方法用來判斷指定的方式是否需要被攔截,程式碼生成器會利用這個方法決定如果生成最終可供攔截的代理類。另一個Validate方法用來驗證針對指定型別的攔截器註冊方式是否合法,即攔截器是否應用到一些根本無法被攔截的方法或者屬性上,具體的檢驗邏輯由方法提供的兩個委託來完成。
我們自定義的IInterceptorProvider實現型別一般派生於如下這個抽象基礎類別InterceptorProviderBase,後者在介面的基礎上提供了一個IConventionalInterceptorFactory介面型別的InterceptorFactory屬性。顧名思義,IConventionalInterceptorFactory物件幫助我們按照約定定義的攔截器型別或者其範例轉換成標準的攔截器表現形式,即InvokeDelegate委託。
public abstract class InterceptorProviderBase : IInterceptorProvider { public IConventionalInterceptorFactory InterceptorFactory { get; } protected InterceptorProviderBase(IConventionalInterceptorFactory interceptorFactory) ; public abstract bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed); public abstract IEnumerable<Sortable<InvokeDelegate>> GetInterceptors(Type targetType, MethodInfo method); } public interface IConventionalInterceptorFactory { InvokeDelegate CreateInterceptor(Type interceptorType, params object[] arguments); InvokeDelegate CreateInterceptor(object interceptor); }
接下來我們通過自定義的IInterceptorProvider型別實現一種「萬能」的攔截器註冊方式——根據指定的條件表示式將指定的攔截器關聯到目標方法上。在提供具體實現之前,我們先來體驗一下由它達成的程式設計模型。
public class FoobarInterceptor { public ValueTask InvokeAsync(InvocationContext invocationContext) { var method = invocationContext.MethodInfo; Console.WriteLine($"{method.DeclaringType!.Name}.{method.Name} is intercepted."); return invocationContext.ProceedAsync(); } } public class Foobar { public virtual void M() { } public virtual object? P { get; set; } }
我們依然以上面這個簡單的攔截器型別FoobarInterceptor為例,現在我們需要將它應用到Foobar型別的M和P屬性的Set方法上,針對FoobarInterceptor的註冊就可以按照如下方式來完成。如程式碼片段所示,我們在呼叫InterceptionBuilder的RegisterInterceptors擴充套件方法中提供了一個Action<ConditionalInterceptorProviderOptions>委託,並利用它新增了針對FoobarInterceptor與兩個Func<Type, MethodInfo, bool>委託之間的關係,後者用來匹配目標方法(含屬性方法)。
var foobar= new ServiceCollection() .AddSingleton<Foobar>() .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)) .GetRequiredService<Foobar>(); foobar.M(); _ = foobar.P; foobar.P = null; Console.ReadLine(); static void RegisterInterceptors(ConditionalInterceptorProviderOptions options) { options.For<FoobarInterceptor>() .To(1, (type, method) => type == typeof(Foobar) && method.Name == "M") .To(1, (type, method) => type == typeof(Foobar) && method.IsSpecialName && method.Name == "set_P"); }
程式執行後會在控制檯輸出如下的結果,可以看出FoobarInterceptor攔截確實只應用到M和P屬性的Set方法上,屬性的Get方法並未被攔截。
上述這種針對匹配條件的「萬能」註冊方式是通過如下這個ConditionalInterceptorProvider型別實現的。ConditionalInterceptorProviderOptions型別定義了對應的設定選項,其核心就是一組ConditionalInterceptorRegistration物件的集合,而每一個ConditionalInterceptorRegistration物件是一個表示匹配條件的Func<Type, MethodInfo, bool>委託與攔截器工廠的Func<IConventionalInterceptorFactory, Sortable<InvokeDelegate>>委託之間的對映關係,後者利用指定的IConventionalInterceptorFactory來建立一個對應的Sortable<InvokeDelegate>物件。
public class ConditionalInterceptorProvider : InterceptorProviderBase { private readonly ConditionalInterceptorProviderOptions _options; public ConditionalInterceptorProvider(IConventionalInterceptorFactory interceptorFactory, IOptions<ConditionalInterceptorProviderOptions> optionsAccessor) : base(interceptorFactory) => _options = optionsAccessor.Value; public override bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed) { suppressed = false; return _options.Registrations.Any(it => it.Condition(targetType, method)); } public override IEnumerable<Sortable<InvokeDelegate>> GetInterceptors(Type targetType, MethodInfo method) => _options.Registrations.Where(it => it.Condition(targetType, method)).Select(it => it.Factory(InterceptorFactory)).ToList(); } public class ConditionalInterceptorProviderOptions { public IList<ConditionalInterceptorRegistration> Registrations { get; } = new List<ConditionalInterceptorRegistration>(); public Registry<TInterceptor> For<TInterceptor>(params object[] arguments)=> new(factory => factory.CreateInterceptor(typeof(TInterceptor), arguments), this); } public class Registry<TInterceptor> { private readonly Func<IConventionalInterceptorFactory, InvokeDelegate> _factory; private readonly ConditionalInterceptorProviderOptions _options; public Registry(Func<IConventionalInterceptorFactory, InvokeDelegate> factory, ConditionalInterceptorProviderOptions options) { _factory = factory; _options = options; } public Registry<TInterceptor> To(int order, Func<Type, MethodInfo, bool> condition) { var entry = new ConditionalInterceptorRegistration(condition, factory=>new Sortable<InvokeDelegate>(order, _factory(factory))); _options.Registrations.Add(entry); return this; } } public class ConditionalInterceptorRegistration { public Func<Type, MethodInfo, bool> Condition { get; } public Func<IConventionalInterceptorFactory, Sortable<InvokeDelegate>> Factory { get; } public ConditionalInterceptorRegistration(Func<Type, MethodInfo, bool> condition, Func<IConventionalInterceptorFactory, Sortable<InvokeDelegate>> factory) { Condition = condition; Factory = factory; } }
這一組對映關係利用ConditionalInterceptorProviderOptions的For<TInterceptor>方法進行新增,該方法返回一個Registry<TInterceptor>物件,後者提供的To方法指定了作為匹配條件的Func<Type, MethodInfo, bool>委託和決定攔截器執行順序的Order值。ConditionalInterceptorProvider利用建構函式注入的IOptions<ConditionalInterceptorProviderOptions>得到這組對映關係,CanIntercept方法利用這組關係的匹配條件確定指定的方法是否應該被攔截,另一個GetInterceptors方法則利用匹配的工廠來建立返回的這組Sortable<InvokeDelegate>物件。
全新升級的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]: 框架設計和實現原理