基于特性標注的攔截器注冊方式僅限于將攔截器應用到自己定義的類型上,對于第三方提供的類型就無能為力了。對于Dora.Interception來說,攔截器注冊本質上建立攔截器與一個或者多個目標方法之間的映射,所以最笨的方式就是利用反射的方式得到表示目標方法的MethodInfo對象,并將它與對應的攔截器關聯在一起。這種方式雖然能夠解決問題,但是編程體驗很差。本篇介紹的基于表達式的攔截器注冊方式利用針對目標方法或者屬性的調用表達式,以一種強類型的方式獲取到目標方法,極大地改善了編程體驗。
一、IInterceptorRegistry
以表達式采用強類型的方式將指定類型的攔截器應用到目標方法上是借助如下這個IInterceptorRegistry接口完成的。IInterceptorRegistry接口提供了一個For<TInterceptor>方法以待注冊的攔截器類型關聯,參數arguments用來提供構建攔截器對象的參數。該方法會返回一個IInterceptorRegistry<TInterceptor>對象,它提供了一系列的方法幫助我們將指定的攔截器應用到指定目標類型(通過泛型參數類型TTarget表示)相應的方法上。
public?interface?IInterceptorRegistry
{IInterceptorRegistry<TInterceptor>?For<TInterceptor>(params?object[]?arguments);...
}public?interface?IInterceptorRegistry<TInterceptor>
{IInterceptorRegistry<TInterceptor>?ToAllMethods<TTarget>(int?order);IInterceptorRegistry<TInterceptor>?ToMethod<TTarget>(int?order,?Expression<Action<TTarget>>?methodCall);IInterceptorRegistry<TInterceptor>?ToMethod(int?order,?Type?targetType,?MethodInfo?method);IInterceptorRegistry<TInterceptor>?ToGetMethod<TTarget>(int?order,?Expression<Func<TTarget,?object?>>?propertyAccessor);IInterceptorRegistry<TInterceptor>?ToSetMethod<TTarget>(int?order,?Expression<Func<TTarget,?object?>>?propertyAccessor);IInterceptorRegistry<TInterceptor>?ToProperty<TTarget>(int?order,?Expression<Func<TTarget,?object?>>?propertyAccessor);
}
封裝了IServiceCollection集合的InterceptionBuilder提供了一個RegisterInterceptors擴展方法,我們可以利用該方法定義的Action<IInterceptorRegistry>類型的參數來使用上述的這個IInterceptorRegistry接口。不論是IServiceCollection接口的BuildInterceptableServiceProvider擴展方法,還是IHostBuilder接口的UseInterception方法均提供了一個可選的Action<InterceptionBuilder>委托類型的參數。
public?sealed?class?InterceptionBuilder
{public?IServiceCollection?Services?{?get;?}public?InterceptionBuilder(IServiceCollection?services);??
}public?static?class?Extensions
{public?static?InterceptionBuilder?RegisterInterceptors(this?InterceptionBuilder?builder,?Action<IInterceptorRegistry>?register);public?static?IServiceProvider?BuildInterceptableServiceProvider(this?IServiceCollection?services,?Action<InterceptionBuilder>??setup?=?null);public?static?IHostBuilder?UseInterception(this?IHostBuilder?hostBuilder,?Action<InterceptionBuilder>??setup?=?null);
}
二、將攔截器應用到某個類型
類似與將InterceptorAttribute標注到某個類型上,我們也可以采用這種方式將指定的攔截器應用到目標類型上,背后的含義就是應用到該類型可以被攔截的所以方法上(含屬性方法)。
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;?}
}
我們可以采用如下的方式將調用IInterceptorRegistry<TInterceptor>的ToAllMethods<TTarget>方法將上面定義的攔截器FoobarInterceptor應用到Foobar類型的所有方法上。
var?foobar?=?new?ServiceCollection().AddSingleton<Foobar>().BuildInterceptableServiceProvider(interception?=>?interception.RegisterInterceptors(RegisterInterceptors)).GetRequiredService<Foobar>();foobar.M();
foobar.P?=?null;
_?=?foobar.P;static?void?RegisterInterceptors(IInterceptorRegistry?registry)
{var?foobar?=?registry.For<FoobarInterceptor>();foobar.ToAllMethods<Foobar>(order:?1);
}
從如下所示的執行結果可以看出,Foobar類型的M方法和P屬性均被FoobarInterceptor攔截下來(源代碼)。
三、應用到指定的方法和屬性
我們可以通過指定調用方法或者獲取屬性的表達式來指定攔截器應用的目標方法。我們將目標類型Foobar定義成如下的形式,兩個重載的M方法和三個屬性均是可以攔截的。
public?class?Foobar
{public?virtual?void?M(int?x,?int?y)?{?}public?virtual?void?M(double?x,?double?y)?{?}public?virtual?object??P1?{?get;?set;?}public?virtual?object??P2?{?get;?set;?}public?virtual?object??P3?{?get;?set;?}
}
我們利用如下的代碼將上面定義的FoobarInterceptor應用到Foobar類型相應的成員上。具體來說,我們調用ToMethod<TTarget>方法應用到兩個重載的M方法,調用ToProperty<TTarget>方法應用到P1屬性的Get和Set方法上,調用ToGetMethod<TTarget>和ToSetMethod<TTarget>方法應用到P2屬性的Get方法和P3屬性的Set方法。
var?provider?=?new?ServiceCollection().AddSingleton<Foobar>().BuildInterceptableServiceProvider(interception?=>?interception.RegisterInterceptors(RegisterInterceptors));var?foobar?=?provider.GetRequiredService<Foobar>();foobar.M(1,?1);
foobar.M(3.14,?3.14);
foobar.P1?=?null;
_?=?foobar.P1;
foobar.P2?=?null;
_?=?foobar.P2;
foobar.P3?=?null;
_?=?foobar.P3;
Console.ReadLine();static?void?RegisterInterceptors(IInterceptorRegistry?registry)
{var?foobar?=?registry.For<FoobarInterceptor>();foobar.ToMethod<Foobar>(order:?1,?it?=>?it.M(default(int),?default(int))).ToMethod<Foobar>(order:?1,?it?=>?it.M(default(double),?default(double))).ToProperty<Foobar>(order:?1,?it?=>?it.P1).ToGetMethod<Foobar>(order:?1,?it?=>?it.P2).ToSetMethod<Foobar>(order:?1,?it?=>?it.P3);
}
程序運行后,針對Foobar相應成員的攔截體現在如下所示的輸出結果上(源代碼)。
四、指定構建攔截器的參數
如果應用的攔截器類型構造函數指定了參數,我們采用這種注冊方式的時候也可以指定參數。以如下這個FoobarInterceptor為例,其構造函數中指定了兩個參數,一個是代表攔截器名稱的name參數,另一個是IFoobar對象。
public?class?FoobarInterceptor
{public?FoobarInterceptor(string?name,?IFoobar?foobar){Name?=?name;Foobar?=?foobar;}public?string?Name?{?get;?}public?IFoobar?Foobar?{?get;?}public?ValueTask?InvokeAsync(InvocationContext?invocationContext){Console.WriteLine($"{invocationContext.MethodInfo.Name}?is?intercepted?by?FoobarInterceptor?{Name}.");Console.WriteLine($"Foobar?is?'{Foobar.GetType()}'.");return?invocationContext.ProceedAsync();}
}
public?interface?IFoobar?{?}
public?class?Foo?:?IFoobar?{?}
public?class?Bar:?IFoobar?{?}public?class?Invoker
{public?virtual?void?M1()?{?}public?virtual?void?M2()?{?}
}
由于字符串參數name無法從依賴注入容器提取,所以在注冊FoobarInterceptor是必須顯式指定。如果容器能夠提供IFoobar對象,但是希望指定一個不通過的對象,也可以在注冊的時候顯式指定一個IFoobar對象。我們按照如下的方式將兩個不同的FoobarInterceptor對象分別應用到Invoker類型的Invoke1和Invoke2方法上,并分別將名稱設置為Interceptor1和Interceptor2,第二個攔截器還指定了一個Bar對象作為參數(容器默認提供的IFoobar對象的類型為Foo)。
var?invoker?=?new?ServiceCollection().AddSingleton<Invoker>().AddSingleton<IFoobar,?Foo>().BuildInterceptableServiceProvider(interception?=>?interception.RegisterInterceptors(RegisterInterceptors)).GetRequiredService<Invoker>();invoker.M1();
Console.WriteLine();
invoker.M2();static?void?RegisterInterceptors(IInterceptorRegistry?registry)
{registry.For<FoobarInterceptor>("Interceptor1").ToMethod<Invoker>(order:?1,?it?=>?it.M1());registry.For<FoobarInterceptor>("Interceptor2",?new?Bar()).ToMethod<Invoker>(order:?1,?it?=>?it.M2());
}
程序運行之后,兩個FoobarInterceptor對象的名稱和依賴的IFoobar對象的類型以如下的形式輸出到控制臺上(源代碼)。
五、攔截屏蔽
除了用來注冊指定攔截器的For<TInterceptor>方法,IInterceptorRegistry接口還定義了如下這些用來屏蔽攔截的SuppressXxx方法。
public?interface?IInterceptorRegistry
{IInterceptorRegistry<TInterceptor>?For<TInterceptor>(params?object[]?arguments);IInterceptorRegistry?SupressType<TTarget>();IInterceptorRegistry?SupressTypes(params?Type[]?types);IInterceptorRegistry?SupressMethod<TTarget>(Expression<Action<TTarget>>?methodCall);IInterceptorRegistry?SupressMethods(params?MethodInfo[]?methods);IInterceptorRegistry?SupressProperty<TTarget>(Expression<Func<TTarget,?object?>>?propertyAccessor);IInterceptorRegistry?SupressSetMethod<TTarget>(Expression<Func<TTarget,?object?>>?propertyAccessor);IInterceptorRegistry?SupressGetMethod<TTarget>(Expression<Func<TTarget,?object?>>?propertyAccessor);
}
我們可以采用如下的方式會將屏蔽掉Foobar類型所有成員的攔截特性,雖然攔截器FoobarInterceptor被注冊到了這個類型上(源代碼)。
var?foobar?=?new?ServiceCollection().AddSingleton<Foobar>().BuildInterceptableServiceProvider(interception?=>?interception.RegisterInterceptors(RegisterInterceptors)).GetRequiredService<Foobar>();
...static?void?RegisterInterceptors(IInterceptorRegistry?registry)
{registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order:?1);registry.SupressType<Foobar>();
}
下面的程序明確屏蔽掉Foobar類型如下這些方法的攔截能力:M方法,P1屬性的Get和Set方法(如果有)以及P屬性的Get方法(源代碼)。
var?foobar?=?new?ServiceCollection().AddSingleton<Foobar>().BuildInterceptableServiceProvider(interception?=>?interception.RegisterInterceptors(RegisterInterceptors)).GetRequiredService<Foobar>();...static?void?RegisterInterceptors(IInterceptorRegistry?registry)
{registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order:?1);registry.SupressMethod<Foobar>(it=>it.M());registry.SupressProperty<Foobar>(it?=>?it.P1);registry.SupressGetMethod<Foobar>(it?=>?it.P2);
}
六、兩個后備方法
通過指定調用目標方法或者提取屬性的表達式來提供攔截器應用的方法和需要屏蔽的方法提供了較好的編程體驗,但是能夠提供這種強類型編程模式的前提是目標方法或者屬性是公共成員。對于受保護(protected)的方法和屬性,我們只能使用如下兩個后備方法,指定代表目標方法的MethodInfo對象。
public?interface?IInterceptorRegistry<TInterceptor>
{IInterceptorRegistry<TInterceptor>?ToMethods<TTarget>(int?order,?params?MethodInfo[]?methods);
}public?interface?IInterceptorRegistry
{IInterceptorRegistry?SupressMethods(params?MethodInfo[]?methods);
}