注册拦截器旨在解决如何将拦截器应用到目标方法的问题。在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少。如果注册的方式过于模糊,很容易将拦截器应用到非目标方法上。按照这个原则,一些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),AddReferencesAddImports方法则用来添加程序集引用和导入命名空间。

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]: 基于策略的拦截器注册方式的更多相关文章

  1. AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册

    按照单一职责的原则,拦截器只负责需要的拦截操作的执行,至于它采用何种方式应用到目标方法上,以及它在整个拦截器管道中的位置则属于“拦截器注册”的范畴.Dora.Interception提供了几种典型的注 ...

  2. AOP框架Dora.Interception 3.0 [1]: 编程体验

    .NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...

  3. AOP框架Dora.Interception 3.0 [2]: 实现原理

    和所有的AOP框架一样,我们必须将正常的方法调用进行拦截,才能将应用到当前方法上的所有拦截器纳入当前调用链.Dora.Interception采用IL Eimit的方式实现对方法调用的拦截,接下来我们 ...

  4. AOP框架Dora.Interception 3.0 [3]: 拦截器设计

    对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上.这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用.从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的 ...

  5. 全新升级的AOP框架Dora.Interception[1]: 编程体验

    多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...

  6. 全新升级的AOP框架Dora.Interception[汇总,共6篇]

    多年之前利用IL Emit写了一个名为Dora.Interception(github地址,觉得不错不妨给一颗星)的AOP框架.前几天利用Roslyn的Source Generator对自己为公司写的 ...

  7. 全新升级的AOP框架Dora.Interception[2]: 基于&ldquo;约定&rdquo;的拦截器定义方式

    Dora.Interception有别于其他AOP框架的最大的一个特点就是采用针对"约定"的拦截器定义方式.如果我们为拦截器定义了一个接口或者基类,那么拦截方法将失去任意注册依赖服 ...

  8. 全新升级的AOP框架Dora.Interception[3]: 基于特性标注的拦截器注册方式

    在Dora.Interception(github地址,觉得不错不妨给一颗星)中按照约定方式定义的拦截器可以采用多种方式注册到目标方法上.本篇文章介绍最常用的基于"特性标注"的拦截 ...

  9. 全新升级的AOP框架Dora.Interception[4]: 基于Lambda表达式的拦截器注册方式

    如果拦截器应用的目标类型是由自己定义的,Dora.Interception(github地址,觉得不错不妨给一颗星)可以在其类型或成员上标注InterceptorAttribute特性来应用对应的拦截 ...

随机推荐

  1. 2019-10-16:maccms10后门复现利用,解析

    该文章仅供学习,利用方法来自网络文章,仅供参考 Maccms10基于php+mysql的maccms,是苹果的内容管理,方便使用,功能良好,适用范围广 后门网站下载网址,是假官网:http://www ...

  2. 2019-9-17:渗透测试,基础学习,apache初识,mysql初识等笔记

    python -m SimpleHTTPServer gedit 文本编辑器 apache2 默认配置文件目录:/etc/apache2/apache2默认首页源码: /var/www/html my ...

  3. HTML、CSS基础知识

    前端基础 1. CSS 8 1.1. CSS叫做层叠样式表,用来设置页面中元素的样式.背景颜色.字体颜色.字体大小... 8 1.2. CSS负责结构.表现.行为中的表现 8 1.3. 编写的位置 8 ...

  4. vue通过控制boolean值来决定是否添加class类名

    vue通过控制boolean值来决定是否添加class类名

  5. PHP基于Redis实现轻量级延迟队列

    延迟队列,顾名思义它是一种带有延迟功能的消息队列. 那么,是在什么场景下我才需要这样的队列呢? 一.背景 先看看一下业务场景: 1.会员过期前3天发送召回通知 2.订单支付成功后,5分钟后检测下游环节 ...

  6. Sql 修改表结构

    添加字段 alter table 表名 add 字段名 nvarchar(100) not null 修改字段 alter table 表名 alter column 字段名 int not null ...

  7. Mybatis拦截器实现原理深度分析

    1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...

  8. 实战webpack系列02

    02. 开始使用webpack 1.1.安装 Webpack可以使用npm安装,新建一个空的练习文件夹(此处命名为webpack sample project),在终端中转到该文件夹后执行下述指令就可 ...

  9. 最短路径算法(跟新SPFA,Ford)

    //以城市路为蓝本介绍算法 1381:城市路(Dijkstra) 时间限制: 1000 ms         内存限制: 65536 KB提交数: 4517     通过数: 1306 [题目描述] ...

  10. 交互程序 Scanner类

    交互程序 1. Scanner类是标准Java类库的类 (1)提供一些方法用于交互式读入不同类型的输入数据,输入可以是不同的数据源,包括用户键入的数据和保存在文件中的数据(怎么弄). (2)将一个字符 ...