注册拦截器旨在解决如何将拦截器应用到目标方法的问题。在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少。如果注册的方式过于模糊,很容易将拦截器应用到非目标方法上。按照这个原则,一些AOP框架提供的针对类型命名空间、类型或者成员名称前(后)缀的拦截器映射策略其实都是不严谨的。Dora.Interception只提供两种严谨的拦截器注册方式,一种前面介绍的针对特性标注的方式,另一种就是本篇介绍的针对策略的方式。
一、AddPolicy
拦截策略表达的是:将一个提供拦截器的IInterceptorProvider对象应用到某个目标类型的某一个或者多个方法或者属性成员上。如下所示的是在《编程体验》中定义的拦截策略,它表达的意图是:将CacheReturnValueAttribute应用到SystemClock类型的GetCurrentTime方法上,并且将Order属性设置为1。
public class Program { public static void Main(string[] args) { Host.CreateDefaultBuilder() .UseInterceptableServiceProvider(configure: Configure) .ConfigureWebHostDefaults(buider => buider.UseStartup()) .Build() .Run(); static void Configure(InterceptionBuilder interceptionBuilder) { interceptionBuilder.AddPolicy(policyBuilder => policyBuilder .For (order: 1, cache => cache .To (target => target .IncludeMethod(clock => clock.GetCurrentTime(default))))); } } }
通过上面的代码片段可以看出,拦截策略是通过调用InterceptionBuilder 的AddPolicy扩展方法注册的。如下面的代码片段所示,该方法具有一个Action
public static partial class InterceptionBuilderExtensions { public static InterceptionBuilder AddPolicy(this InterceptionBuilder builder, Actionconfigure); }
二、IInterceptionPolicyBuilder
Dora.Interception最终利用InterceptionPolicy对象来表示拦截策略,如下面的代码片段所示,IInterceptionPolicyBuilder的Build方法最终会生成该对象。具体针对拦截策略的定义体现在针对For
public interface IInterceptionPolicyBuilder { IServiceProvider ServiceProvider { get; } InterceptionPolicy Build(); IInterceptionPolicyBuilder For(int order, Action configureTargets, params object[] arguments) where TInterceptorProvider: IInterceptorProvider; }
For
三、IInterceptorProviderPolicyBuilder
IInterceptorProviderPolicyBuilder的To
public interface IInterceptorProviderPolicyBuilder { InterceptorProviderPolicy Build(); IInterceptorProviderPolicyBuilder To(Action > configure); }
四、ITargetPolicyBuilder
ITargetPolicyBuilder
public interface ITargetPolicyBuilder{ TargetTypePolicy Build(); ITargetPolicyBuilder IncludeAllMembers(); ITargetPolicyBuilder IncludeMethod(Expression > methodInvocation); ITargetPolicyBuilder ExcludeMethod(Expression > methodInvocation); ITargetPolicyBuilder IncludeProperty (Expression > propertyAccessor, PropertyMethod propertyMethod); ITargetPolicyBuilder ExcludeProperty (Expression > propertyAccessor, PropertyMethod propertyMethod); } [Flags] public enum PropertyMethod { Get = 1, Set = 2, Both = 3 }
五、一个完整的拦截策略
通过上面Dora.Interception提供的API,基本上能够将任何请问的拦截器注册需求定义成相应的拦截策略。如下所示的拦截策略综合使用了上述所有的方法。
public static void Main(string[] args) { Host.CreateDefaultBuilder() .UseInterceptableServiceProvider(configure: Configure) .ConfigureWebHostDefaults(buider => buider.UseStartup()) .Build() .Run(); static void Configure(InterceptionBuilder buidler) => buidler.AddPolicy(policy => policy .For (1, interceptor => interceptor .To (target => target .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set))) .For (2, interceptor => interceptor .To (target => target .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set))) .For (3, interceptor => interceptor .To (target => target .IncludeAllMembers() .ExcludeMethod(foobar => foobar.NonInterceptableInvokeAsync()) .ExcludeProperty(foobar => foobar.NonInterceptable, PropertyMethod.Both) .ExcludeProperty(foobar => foobar.Get, PropertyMethod.Set) .ExcludeProperty(foobar => foobar.Set, PropertyMethod.Get)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)))); }
六、策略脚本化
考虑到拦截策略可能需要动态调整,但是我们又不希望对应用进行重新编译和发布,所以我们可以考虑将拦截策略定义在配置文件中。但是配置文件在表达“目标成员选择”方面会很繁琐,因为如果好标识某个方法,不仅需要指定方法名称,还需要指定所有参数列表类型。我们最终将拦截策略定义成C#脚本来解决这个问题。如果已经将拦截策略定义在一个C#脚本文件中,我们可以调用InterceptionBuilder如下这个AddPolicy扩展方法重载。
public static partial class InterceptionBuilderExtensions { public static InterceptionBuilder AddPolicy(this InterceptionBuilder builder, string fileName, Actionconfigure = null); }
除了指定作为策略文件的路径之外,我们还可以提供一个Action
public sealed class PolicyFileBuilder { public IFileProvider FileProvider {get; } public Assembly[] References {get; } public string[] Imports {get; } public PolicyFileBuilder SetFileProvider(IFileProvider fileProvider); public PolicyFileBuilder AddReferences(params Assembly[] references); public PolicyFileBuilder AddImports(params string[] namespaces); public string ReadAllText(string fileName); }
当我们在定义拦截策略脚本的时候,它可以获取一个用来构建拦截器策略的IInterceptionPolicyBuilder对象的全局变量,该全局变量被命名为policyBuilder。对于上面一节中定义的拦截策略,我们可以采用如下的方式将它脚本话,两者的内容几乎是完全一致的。
policyBuilder .For(1, interceptor => interceptor .To (target => target .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set))) .For (2, interceptor => interceptor .To (target => target .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set))) .For (3, interceptor => interceptor .To (target => target .IncludeAllMembers() .ExcludeMethod(foobar => foobar.NonInterceptableInvokeAsync()) .ExcludeProperty(foobar => foobar.NonInterceptable, PropertyMethod.Both) .ExcludeProperty(foobar => foobar.Get, PropertyMethod.Set) .ExcludeProperty(foobar => foobar.Set, PropertyMethod.Get)) .To (targetBuilder => targetBuilder .IncludeMethod(foobar => foobar.InterceptableInvokeAsync()) .IncludeProperty(foobar => foobar.Both, PropertyMethod.Both) .IncludeProperty(foobar => foobar.Get, PropertyMethod.Get) .IncludeProperty(foobar => foobar.Set, PropertyMethod.Set)));
AOP框架Dora.Interception 3.0 [1]: 编程体验
AOP框架Dora.Interception 3.0 [2]: 实现原理
AOP框架Dora.Interception 3.0 [3]: 拦截器设计
AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册
AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册
AOP框架Dora.Interception 3.0 [6]: 自定义拦截器注册方式