AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式
注册拦截器旨在解决如何将拦截器应用到目标方法的问题。在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少。如果注册的方式过于模糊,很容易将拦截器应用到非目标方法上。按照这个原则,一些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<Startup>())
.Build()
.Run(); static void Configure(InterceptionBuilder interceptionBuilder)
{
interceptionBuilder.AddPolicy(policyBuilder => policyBuilder
.For<CacheReturnValueAttribute>(order: , cache => cache
.To<SystemClock>(target => target
.IncludeMethod(clock => clock.GetCurrentTime(default)))));
}
}
}
通过上面的代码片段可以看出,拦截策略是通过调用InterceptionBuilder 的AddPolicy扩展方法注册的。如下面的代码片段所示,该方法具有一个Action<IInterceptionPolicyBuilder>类型的参数,具体的拦截策略最终是利用IInterceptionPolicyBuilder对象来定义的。
public static partial class InterceptionBuilderExtensions
{
public static InterceptionBuilder AddPolicy(this InterceptionBuilder builder, Action<IInterceptionPolicyBuilder> configure);
}
二、IInterceptionPolicyBuilder
Dora.Interception最终利用InterceptionPolicy对象来表示拦截策略,如下面的代码片段所示,IInterceptionPolicyBuilder的Build方法最终会生成该对象。具体针对拦截策略的定义体现在针对For<TInterceptorProvider>方法的调用上。拦截策略是以提供拦截器的IInterceptorProvider对象来基础,For<TInterceptorProvider>方法直接利用泛型参数来提供具体的IInterceptorProvider类型。
public interface IInterceptionPolicyBuilder
{
IServiceProvider ServiceProvider { get; }
InterceptionPolicy Build();
IInterceptionPolicyBuilder For<TInterceptorProvider>(int order, Action<IInterceptorProviderPolicyBuilder> configureTargets, params object[] arguments) where TInterceptorProvider: IInterceptorProvider;
}
For<TInterceptorProvider>方法的Order属性表示提供拦截器在最终拦截器管道的位置,最终的arguments参数用来提供创建拦截器对象时所需的参数列表。如果构造函数的所有参数都可以利用依赖注入容器(对应于IInterceptionPolicyBuilder的ServiceProvider属性)来提供,这个参数是可以缺省的。For<TInterceptorProvider>方法的核心是作为第二个参数的Action<IInterceptorProviderPolicyBuilder> 对象,它决定了指定的IInterceptorProvider应该应用到那个类型的哪些成员上。
三、IInterceptorProviderPolicyBuilder
IInterceptorProviderPolicyBuilder的To<TTarget>方法会将当前指定的IInterceptorProvider应用到通过泛型参数表示的目标类型上,至于具体应用到哪些方法或者属性成员上,则由提供的Action<ITargetPolicyBuilder<TTarget>> 对象作进一步设置。
public interface IInterceptorProviderPolicyBuilder
{
InterceptorProviderPolicy Build();
IInterceptorProviderPolicyBuilder To<TTarget>(Action<ITargetPolicyBuilder<TTarget>> configure);
}
四、ITargetPolicyBuilder<T>
ITargetPolicyBuilder<T>(泛型类型表示IInterceptorProvider应用的目标类型)旨在解决成员选择的问题。我们可以调用IncludeMethod或者IncludeProperty<TValue>显式指定目标方法或者属性。如果我们需要应用到所有可被拦截的方法和属性,可以调用IncludeAllMembers方法,如果需要排除少数几个方法或者属性成员,可以调用ExcludeMethod或者ExcludeMethod<TValue>方法。由于这些方法利用表达式而不是名称来选择目标成员,所以它不但能够避免方法名称写错的情况,还能解决方法重载的问题。
public interface ITargetPolicyBuilder<T>
{
TargetTypePolicy Build();
ITargetPolicyBuilder<T> IncludeAllMembers();
ITargetPolicyBuilder<T> IncludeMethod(Expression<Action<T>> methodInvocation);
ITargetPolicyBuilder<T> ExcludeMethod(Expression<Action<T>> methodInvocation);
ITargetPolicyBuilder<T> IncludeProperty<TValue>(Expression<Func<T, TValue>> propertyAccessor, PropertyMethod propertyMethod);
ITargetPolicyBuilder<T> ExcludeProperty<TValue>(Expression<Func<T, TValue>> propertyAccessor, PropertyMethod propertyMethod);
} [Flags]
public enum PropertyMethod
{
Get = ,
Set = ,
Both =
}
五、一个完整的拦截策略
通过上面Dora.Interception提供的API,基本上能够将任何请问的拦截器注册需求定义成相应的拦截策略。如下所示的拦截策略综合使用了上述所有的方法。
public static void Main(string[] args)
{
Host.CreateDefaultBuilder()
.UseInterceptableServiceProvider(configure: Configure)
.ConfigureWebHostDefaults(buider => buider.UseStartup<Startup>())
.Build()
.Run(); static void Configure(InterceptionBuilder buidler) => buidler.AddPolicy(policy => policy
.For<FooInterceptorAttribute>(, interceptor => interceptor
.To<FoobarService>(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<FoobazService>(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<BarInterceptorAttribute>(, interceptor => interceptor
.To<FoobarService>(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<BarbazService>(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<BazInterceptorAttribute>(, interceptor => interceptor
.To<FoobazService>(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<BarbazService>(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, Action<PolicyFileBuilder> configure = null);
}
除了指定作为策略文件的路径之外,我们还可以提供一个Action<PolicyFileBuilder>对象。如下面的代码片段所示,PolicyFileBuilder提供了三个方法,SetFileProvider方法用来设置用来读取拦截策略文件的IFileProvider对象(默认为针对当前目录的PhysicalFileProvider),AddReferences和AddImports方法则用来添加程序集引用和导入命名空间。
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<FooInterceptorAttribute>(, interceptor => interceptor
.To<FoobarService>(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<FoobazService>(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<BarInterceptorAttribute>(, interceptor => interceptor
.To<FoobarService>(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<BarbazService>(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<BazInterceptorAttribute>(, interceptor => interceptor
.To<FoobazService>(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<BarbazService>(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]: 自定义拦截器注册方式
AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式的更多相关文章
- AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册
按照单一职责的原则,拦截器只负责需要的拦截操作的执行,至于它采用何种方式应用到目标方法上,以及它在整个拦截器管道中的位置则属于“拦截器注册”的范畴.Dora.Interception提供了几种典型的注 ...
- AOP框架Dora.Interception 3.0 [1]: 编程体验
.NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...
- AOP框架Dora.Interception 3.0 [2]: 实现原理
和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...
- AOP框架Dora.Interception 3.0 [3]: 拦截器设计
对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上.这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用.从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的 ...
- 全新升级的AOP框架Dora.Interception[1]: 编程体验
多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...
- 全新升级的AOP框架Dora.Interception[汇总,共6篇]
多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...
- 全新升级的AOP框架Dora.Interception[2]: 基于“约定”的拦截器定义方式
Dora.Interception有别于其他AOP框架的最大的一个特点就是采用针对"约定"的拦截器定义方式.如果我们为拦截器定义了一个接口或者基类,那么拦截方法将失去任意注册依赖服 ...
- 全新升级的AOP框架Dora.Interception[3]: 基于特性标注的拦截器注册方式
在Dora.Interception(github地址,觉得不错不妨给一颗星)中按照约定方式定义的拦截器可以采用多种方式注册到目标方法上.本篇文章介绍最常用的基于"特性标注"的拦截 ...
- 全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式
如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截 ...
随机推荐
- Java描述设计模式(24):备忘录模式
本文源码:GitHub·点这里 || GitEE·点这里 一.生活场景 1.场景描述 常见的视频播放软件都具备这样一个功能:假设在播放视频西游记,如果这时候切换播放视频红楼梦,当再次切回播放西游记时, ...
- Java基础面试题及答案(二)
容器 18. java 容器都有哪些? 常用容器的图录: 19. Collection 和 Collections 有什么区别? java.util.Collection 是一个集合接口(集合类的一个 ...
- 配置Tomcat监听80端口、配置Tomcat虚拟主机、Tomcat日志
6月27日任务 16.4 配置Tomcat监听80端口16.5/16.6/16.7 配置Tomcat虚拟主机16.8 Tomcat日志扩展邱李的tomcat文档 https://www.linuser ...
- shell脚本中的逻辑判断、文件目录属性判断、if特殊用法、case判断
7月12日任务 20.5 shell脚本中的逻辑判断20.6 文件目录属性判断20.7 if特殊用法20.8/20.9 case判断 20.5 shell脚本中的逻辑判断 逻辑判断在shell中随处可 ...
- Java语法进阶12-集合
集合 集合:是一种容器,用来装对象的容器,不能装基本数据类型. 数组也是容器,可以用来装基本数据类型,也可以用来装对象. 本质上,集合需要用对应的数据结构实现,是多个类实现接口Collection系列 ...
- 【nodejs原理&源码赏析(3)】欣赏手术级的原型链加工艺术
[摘要] 学习经典代码中的prototype加工 示例代码托管在:http://www.github.com/dashnowords/blogs 好的代码都差不多,烂的代码却各有各的烂法. 一. 概述 ...
- 挑战10个最难的Java面试题(附答案)【上】
欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),验证通过后,输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动&quo ...
- Python面试180道题
版权声明:本文为CSDN博主「CSDN学院官方账号」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/csd ...
- 最强Java并发编程详解:知识点梳理,BAT面试题等
本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...
- go基础之基本数据结构(数组、slice、map)
go基本的数据结构有数组.slice.map,高级数据结构为结构体为用户自定义类型.本片文章主要讲解三大基本数据结构. 数组 slice Map 数组 数组是包含单个类型的元素序列,但是长度固定的数据 ...