全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式
如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截器。如果对那个的程序集是由第三方提供的呢?此时我们可以采用提供的第二种基于表达式的拦截器应用方式。这里的拦截器是一个调用目标类型某个方法或者提取某个属性的Lambda表达式,我们采用这种强类型的编程方式得到目标方法,并提升编程体验。(拙著《ASP.NET Core 6框架揭秘》于日前上市,加入享6折优惠)
目录
一、IInterceptorRegistry
二、将拦截器应用到某个类型
三、应用到指定的方法和属性
四、指定构建拦截器的参数
五、拦截屏蔽
六、两个后备方法
一、IInterceptorRegistry
以表达式采用强类型的方式将指定类型的拦截器应用到目标方法上是借助如下这个IInterceptorRegistry接口完成的。IInterceptorRegistry接口提供了一个For
public interface IInterceptorRegistry { IInterceptorRegistryFor (params object[] arguments); ... } public interface IInterceptorRegistry { IInterceptorRegistry ToAllMethods (int order); IInterceptorRegistry ToMethod (int order, Expression > methodCall); IInterceptorRegistry ToMethod(int order, Type targetType, MethodInfo method); IInterceptorRegistry ToGetMethod (int order, Expression object?>> propertyAccessor); IInterceptorRegistry ToSetMethod (int order, Expression object?>> propertyAccessor); IInterceptorRegistry ToProperty (int order, Expression object?>> propertyAccessor); }
封装了IServiceCollection集合的InterceptionBuilder提供了一个RegisterInterceptors扩展方法,我们可以利用该方法定义的Action
public sealed class InterceptionBuilder { public IServiceCollection Services { get; }
public InterceptionBuilder(IServiceCollection services); } public static class Extensions { public static InterceptionBuilder RegisterInterceptors(this InterceptionBuilder builder, Actionregister); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action ? setup = null); public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, Action ? 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
var foobar = new ServiceCollection() .AddSingleton() .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)) .GetRequiredService (); foobar.M(); foobar.P = null; _ = foobar.P; static void RegisterInterceptors(IInterceptorRegistry registry) { var foobar = registry.For (); foobar.ToAllMethods (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
var provider = new ServiceCollection() .AddSingleton() .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)); var foobar = provider.GetRequiredService (); 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 (); foobar .ToMethod (order: 1, it => it.M(default(int), default(int))) .ToMethod (order: 1, it => it.M(default(double), default(double))) .ToProperty (order: 1, it => it.P1) .ToGetMethod (order: 1, it => it.P2) .ToSetMethod (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() .AddSingleton () .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)) .GetRequiredService (); invoker.M1(); Console.WriteLine(); invoker.M2(); static void RegisterInterceptors(IInterceptorRegistry registry) { registry.For ("Interceptor1").ToMethod (order: 1, it => it.M1()); registry.For ("Interceptor2", new Bar()).ToMethod (order: 1, it => it.M2()); }
程序运行之后,两个FoobarInterceptor对象的名称和依赖的IFoobar对象的类型以如下的形式输出到控制台上(源代码)。
五、拦截屏蔽
除了用来注册指定拦截器的For
public interface IInterceptorRegistry { IInterceptorRegistryFor (params object[] arguments); IInterceptorRegistry SupressType (); IInterceptorRegistry SupressTypes(params Type[] types); IInterceptorRegistry SupressMethod (Expression > methodCall); IInterceptorRegistry SupressMethods(params MethodInfo[] methods); IInterceptorRegistry SupressProperty (Expression object?>> propertyAccessor); IInterceptorRegistry SupressSetMethod (Expression object?>> propertyAccessor); IInterceptorRegistry SupressGetMethod (Expression object?>> propertyAccessor); }
我们可以采用如下的方式会将屏蔽掉Foobar类型所有成员的拦截特性,虽然拦截器FoobarInterceptor被注册到了这个类型上(源代码)。
var foobar = new ServiceCollection() .AddSingleton() .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)) .GetRequiredService (); ... static void RegisterInterceptors(IInterceptorRegistry registry) { registry.For ().ToAllMethods (order: 1); registry.SupressType (); }
下面的程序明确屏蔽掉Foobar类型如下这些方法的拦截能力:M方法,P1属性的Get和Set方法(如果有)以及P属性的Get方法(源代码)。
var foobar = new ServiceCollection() .AddSingleton() .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors)) .GetRequiredService (); ... static void RegisterInterceptors(IInterceptorRegistry registry) { registry.For ().ToAllMethods (order: 1); registry.SupressMethod (it=>it.M()); registry.SupressProperty (it => it.P1); registry.SupressGetMethod (it => it.P2); }
六、两个后备方法
通过指定调用目标方法或者提取属性的表达式来提供拦截器应用的方法和需要屏蔽的方法提供了较好的编程体验,但是能够提供这种强类型编程模式的前提是目标方法或者属性是公共成员。对于受保护(protected)的方法和属性,我们只能使用如下两个后备方法,指定代表目标方法的MethodInfo对象。
public interface IInterceptorRegistry{ IInterceptorRegistry ToMethods (int order, params MethodInfo[] methods); } public interface IInterceptorRegistry { IInterceptorRegistry SupressMethods(params MethodInfo[] methods); }