在《以約定的方式定義攔截器》中,我們通過對攔截器的介紹了Dora.Interception的兩種攔截機制,即針對接口的“實例攔截”針對虛方法的“類型攔截”。我們介紹了攔截器的本質以及基於約定的攔截器定義方式,接下來我們將着重關注攔截器的應用問題。
一、攔截器應用解決什麼問題
和有些AOP框架不同,Dora.Interception在設計刻意地將攔截器和攔截器應用刻意地分開。不僅如此,在攔截器和攔截器應用之間,我們還分離出“攔截器管道的構建”:
- 攔截器:旨在完成單一攔截功能的實現;
- 攔截器管道的構建:將多個攔截器按照指定的順序構建一個管道;
- 攔截器的應用:將構建的攔截器管道應用到被攔截的某個方法上。
二、IInterceptorChainBuilder
攔截器管道的構建由IInterceptorChainBuilder來完成,它類似於ASP.NET Core的IApplicationBuilder接口,後者利用註冊的中間件來構建一箇中間件管道,而IInterceptorChainBuilder則採用類似的方式將註冊的攔截器構建成一個攔截器管道。如下面的代碼片段所示,我們利用Use方法將表示攔截器的InterceptorDelegate 的委託對象提供給IInterceptorChainBuilder,該方法的order參數表示提供的攔截器最終在攔截器鏈條上的位置。攔截器管道的構建最終由Build方法來完成,構建的管道也體現爲一個InterceptorDelegate類型的委託。
public interface IInterceptorChainBuilder { InterceptorDelegate Build(); IInterceptorChainBuilder New(); IInterceptorChainBuilder Use(InterceptorDelegate interceptor, int order); IServiceProvider ServiceProvider { get; } }
由於Dora.Interception是爲.NET Core度身定製的,而.NET Core總是離不開通過通過IServiceProvider表示的DI容器,所以我們將IServiceProvider整合到IInterceptorChainBuilder中,我們在構建攔截器管道過程中所需的任何一個依賴服務都可以利用它來提取。IInterceptorChainBuilder的New方法用來創建一個新的IInterceptorChainBuilder對象,當我們開始構建一個管道的時候需要調用此方法。
雖然Dora.Interception最終總是利用InterceptorDelegate對象來表示攔截器,但是我們推薦應用程序採用我們約定的方式將攔截器定義成一個POCO類型,所謂我們爲IInterceptorChainBuilder定義瞭如下幾個擴展方法來註冊一次方式定義的攔截器類型。如果調用第一個和第三個Use方法提供攔截器類型(第二個Use方法直接提供的是攔截器對象),我們最終需要利用作爲DI容器的IServiceProvider對象來創建對應的實例。如果構造函數中所有的參數都是預選註冊的服務,我們無需提供任何的參數,否則就需要利用arguments來提供它們。
public static class InterceptorChainBuilderExtensions { public static IInterceptorChainBuilder Use<TInterceptor>(this IInterceptorChainBuilder builder, int order, params object[] arguments); public static IInterceptorChainBuilder Use(this IInterceptorChainBuilder builder, object interceptor, int order); public static IInterceptorChainBuilder Use(this IInterceptorChainBuilder builder, Type interceptorType, int order, params object[] arguments); }
三、IInterceptorProvider
IInterceptorChainBuilder僅僅是一個用來構建攔截器管道的工具而已,最終向它提供原材料(攔截器)的是一個IInterceptorProvider對象。一般來說一個攔截器類型對應一個IInterceptorProvider實現(也可可以是多個)。如下面的代碼片段所示,IInterceptorProvider同樣定義了一個Use方法,該方法將上面這個IInterceptorChainBuilder作爲參數。在具體的實現中,我們一般會調用IInterceptorChainBuilder的Use方法來註冊對應的攔截器類型。IInterceptorProvider還具有一個AllowMultiple屬性表示當前類型的多個攔截器實例能夠同時存在於管道中。
public interface IInterceptorProvider { void Use(IInterceptorChainBuilder builder); bool AllowMultiple { get; } }
IInterceptorProvider接口實際上體現了攔截器的註冊方法,因爲所謂的攔截器註冊本質上體現爲如何向IInterceptorChainBuilder提供攔截器的問題。由於標準Attribute是我們推薦的註冊方式,我們爲它們定義瞭如下這個名爲InterceptorAttribute的基類。InterceptorAttribute可以標註到類型、屬性和方法上,它的AllowMultiple屬性與標註到該Attribute上的AttributeUsageAttribute的AllowMultiple屬性一致,默認值爲False。
[AttributeUsage((AttributeTargets) (AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Class), AllowMultiple=false)] public abstract class InterceptorAttribute : Attribute, IInterceptorProvider, { public abstract void Use(IInterceptorChainBuilder builder); public bool AllowMultiple { get; } public int Order { get; set; } }
比如在前面一章中,我們定義瞭如下這麼一個典型的Interceptor類型:
public class FoobarInterceptor { public IFoo Foo { get; } public string Baz { get; } public FoobarInterceptor(IFoo foo, string baz) { Foo = foo; Baz = baz; } public async Task InvokeAsync(InvocationContext context, IBar bar) { await Foo.DoSomethingAsync(); await bar.DoSomethingAsync(); await context.ProceedAsync(); } }
我們可以爲它定義如下這麼一個類型爲FoobarInterceptorAttribute 的IInterceptorProvider的實現。
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Method)] public class FoobarInterceptorAttribute : InterceptorAttribute { public string Baz { get; } public FoobarInterceptorAttribute(string baz) => Baz = baz; public override void Use(IInterceptorChainBuilder builder) => builder.Use<FoobarInterceptor>(Order, Baz); }
雖然Dora.Interception是將Interceptor和IInterceptorProvider區分開來,但是應用程序可以採用如下的方式將它們合二爲一。其實將它們分而治之還有一個好處,那就是我可以爲IInterceptorProvider起一個不同的名稱,比如第一篇演示的實例中我們將攔截器命名爲CachingInterceptor,但是對應的IInterceptorProvider實現類型則定義成CacheReturnValueAttribute。還有另一個好處就是可以爲同一個攔截器名類型定義多一個不同的IInterceptorProvider實現。
public class FoobarInterceptorAttribute : InterceptorAttribute { public string Baz { get; } public FoobarInterceptorAttribute(string baz) => Baz = baz; public async Task InvokeAsync(InvocationContext context, IFoo foo, IBar bar) { await foo.DoSomethingAsync(); await bar.DoSomethingAsync(); await context.ProceedAsync(); } public override void Use(IInterceptorChainBuilder builder) => builder.Use(this, Order); }
四、IInterceptorProviderResolver
最終針對攔截器的應用體現在IInterceptorProviderResolver對象上。IInterceptorProvider其實幫助我們解決了一個核心問題:提供具體的攔截器並將它存放到對應的位置(即在最終構建的攔截器管道中的Order)。那麼針對攔截器的應用最終體現爲:針對一個類型或者其成員(方法或者屬性),能夠提供怎樣的IInterceptorProvider。如下面的代碼片段所示,IInterceptorProviderResolver提供了三個方法來解析應用到類型、方法和屬性(Get、Set或者Both)的IInterceptorProvider。
public interface IInterceptorProviderResolver { bool? WillIntercept(Type targetType); IInterceptorProvider[] GetInterceptorProvidersForMethod(Type targetType, MethodInfo targetMethod); IInterceptorProvider[] GetInterceptorProvidersForProperty(Type targetType, PropertyInfo targetProperty, PropertyMethod getOrSet); IInterceptorProvider[] GetInterceptorProvidersForType(Type targetType); } [Flags] public enum PropertyMethod { Get = 1, Set = 2, Both = 3, }
我們說標註Attribute僅僅體現爲針對攔截器的一種註冊方式而已,因爲在Dora.Interception我們爲它定義瞭如下這麼一個AttributeInterceptorProviderResolver,它是默認註冊的。
internal class AttributeInterceptorProviderResolver : IInterceptorProviderResolver { public IInterceptorProvider[] GetInterceptorProvidersForMethod(Type targetType, MethodInfo method); public IInterceptorProvider[] GetInterceptorProvidersForProperty(Type targetType, PropertyInfo property, PropertyMethod propertyMethod); public IInterceptorProvider[] GetInterceptorProvidersForType(Type type); public bool? WillIntercept(Type type); public bool? WillIntercept(Type targetType, MethodInfo method); public bool? WillIntercept(Type targetType, PropertyInfo property); }
五、自行實現你需要的攔截器應用方式
如果我們覺得基於Attribute的實現不能滿足你的需求,只需要自行實現上面這個IInterceptorProviderResolver接口就可以了。比如我們可以定義如下這個“萬能”的IInterceptorProviderResolver實現,因爲我將針對IInterceptorProvider對象與目標方法的匹配規則定義成一個Func<MethodInfo, bool>。
public class InterceptorRegistry : IInterceptorProviderResolver { private readonly IInterceptorProvider[] _empty = new IInterceptorProvider[0]; private readonly Dictionary<IInterceptorProvider, Func<MethodInfo, bool>> _policies = new Dictionary<IInterceptorProvider, Func<MethodInfo, bool>>(); public IInterceptorProvider[] GetInterceptorProvidersForMethod(Type targetType, MethodInfo targetMethod) => _policies.Where(it => it.Value(targetMethod)).Select(it => it.Key).ToArray(); public IInterceptorProvider[] GetInterceptorProvidersForProperty(Type targetType, PropertyInfo targetProperty, PropertyMethod getOrSet) { switch (getOrSet) { case PropertyMethod.Get: return GetInterceptorProvidersForMethod(targetType, targetProperty.GetMethod); case PropertyMethod.Set: return GetInterceptorProvidersForMethod(targetType, targetProperty.SetMethod); default: return GetInterceptorProvidersForMethod(targetType, targetProperty.GetMethod) .Union(GetInterceptorProvidersForMethod(targetType, targetProperty.SetMethod)) .ToArray(); } } public IInterceptorProvider[] GetInterceptorProvidersForType(Type targetType) => _empty; public bool? WillIntercept(Type targetType) { if (targetType.GetCustomAttributes<NonInterceptableAttribute>().Any()) { return false; } return null; } public InterceptorRegistry Add(IInterceptorProvider interceptorProvider, Func<MethodInfo, bool> filter) { _policies.Add(interceptorProvider, filter); return this; } }
這樣的自定義IInterceptorProviderResolver(InterceptorRegistry )可以採用如下的方式進行註冊。
public class Startup { public void ConfigureServices(IServiceCollection services) { var registry = new InterceptorRegistry() .Add(new CacheReturnValueAttribute(), method => method.Name == "GetCurrentTime" && method.DeclaringType == typeof(SystemClock)); services.AddInterception(builder=>builder.InterceptorProviderResolvers.Add("policy",registry)); } ... }
或者
public class Startup { public IServiceProvider ConfigureServices(IServiceCollection services) { var registry = new InterceptorRegistry() .Add(new CacheReturnValueAttribute(), method => method.Name == "GetCurrentTime" && method.DeclaringType == typeof(SystemClock));
通過自定義IInterceptorProviderResolver可以幫助我們實現任意形式的攔截器註冊方式,但是千萬不能濫用。我個人的觀點是:這種用於註冊攔截器的規則必需是明確的,我們必需非常確切地知道攔截器最終應用到了哪個方法上。如果定義的規則太過模糊,比如針對方法名稱進行註冊,那麼我們的攔截器極有可能應用到某個我們並不希望的方法上。
[1]:更加簡練的編程體驗 [2]:基於約定的攔截器定義方式 [3]:多樣性的攔截器應用方式 [4]:與依賴注入框架的深度整合 [5]:對攔截機制的靈活定製