Dora.Interception, 为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式
相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式。我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口,Dora.Interception下的Interceptor定义变得更加自由。除此之外,Interceptor的异步执行是我在设计Dora.Interception之初最为关心的问题,也就是说如果Interceptor应用的目标方法是异步的,Interceptor自身也应该被赋予异步执行的能力。接下来我们就来聊聊如果你使用了Dora.Interception,如何定义你的Interceptor。
目录 一、两种代理类型生成方式 二、InterceptorDelegate 三、定义Interceptor类型 四、支持方法注入
一、两种代理类型生成方式
Dora.Interception采用动态生成代理类型(采用IL Emit)的方式来实现针对目标方法的拦截,具体来说我们提供了两种类型的代理类型生成方式:
- 如果目标类型实现了某个接口,我们会让生成的代理类型实现这个接口,在代理类型中实现的接口方法会执行拦截操作,这意味着定义在接口中的所有方法都是可以被拦截的。代理对象会封装目标对象,如果需要最终调用目标方法,被封装的这个目标对象相应的方法会被调用。我们将这种形式的代理类型生成方式成为“基于接口的代码生成”。
- 如果目标类型没有实现接口,那么生成的代理类型会直接派生于这个类型,如果定义在基类中的某个虚方法需要被拦截,我们会在代理类中通过重写该方法来执行拦截操作。针对目标方法的调用可以通过调用基类对应的方法来实现。我们将这种形式的代理类型生成方式成为“基于虚方法的代码生成”。
二、InterceptorDelegate
AOP的核心在于将一些非业务的功能定义成相应的Interceptor,并以横切(Crosscutting)的形式注入到针对目标方法的调用过程中。换句话说,Interceptor需要拦截目标方法的执行,并在针对当前执行方法的上下文中执行被注入的操作。如果我们将方法执行的上下文定义成一个InvocationContext,那么Interceptor需要执行的拦截操作就可以表示成一个Action<InvocationContext>。但是我们在上面说过了,一个Interceptor应该被赋予异步执行的能力,按照基于Task的并行编程模式,Interceptor自身执行的拦截操作应该表示成一个Func<InvocationContext, Task>。
我们在Dora.Interception定义了如下这个抽象的InvocationContext来表示需要被拦截的方法执行上下文。它的Method和TargetMethod返回代表当前方法的MethodBase对象,如果采用基于接口的代理类型生成方式,前者表示定义在接口上的方法,后者则表示定义在目标类型上的方法。如果采用基于虚方法的代理类型生成方式,两个属性返回的是同一个对象,表示定义在被拦截类型中的方法。至于Proxy和Target则很明显,分别表示当前的代理对象和被封装的目标对象,如果采用基于虚方法的代理类型生成方式,两个属性返回同一个对象。
1 public abstract class InvocationContext
2 {
3 public abstract MethodBase Method { get; }
4 public MethodBase TargetMethod { get; }
5 public abstract object Proxy { get; }
6 public abstract object Target { get; }
7 public abstract object[] Arguments { get; }
8 public abstract object ReturnValue { get; set; }
9 }
InvocationContext的Arguments表示当前方法调用的参数,既包括一般的输入参数,也包括ref/out参数。值得一提的是,Arguments属性是可读可写的,也就说Interceptor可以通过修改该属性中某个元素的值进而实现修改某个参数值的目的。由于整个调用过程共享同一个InvocationContext对象,所以某个Interceptor对Arguments所作的任何修改都将影响到后续Intercecptor以及最终目标方法的执行。InvocationContext的ReturnValue表示方法调用的返回值。如果目标方法最终被调用,它的返回值将最终反映在这个属性上。这个属性是可读可写的,任意Interceptor都可以通过修改这个属性得到改变方法调用返回值的目的。
由于当前方法调用的执行上下文被表示成一个InvocationContext对象,所以实现在Interceptor上的拦截操作可以表示成一个Func<InvocationContext, Task>类型的委托。不过为了编程方便,我们专门定义了如下这个对应的委托类型InterceptDelegate。由于一个方法上可以同时应用多个Interceptor,那么对应一个Interceptor在完成了自身定义的拦截操作之后,它还将决定是否继续调用后续的Interceptor或者目标方法,或者说针对后续Interceptor或者目标方法的调用也属于当前拦截操作的一部分,所以我们定义了另一个委托类型InterceptorDelegate来表示一个Interceptor对象。
1 public delegate Task InterceptDelegate(InvocationContext context);
2 public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);
对于表示Interceptor的InterceptorDelegate委托,它的输入和输出都是InterceptDelegate委托,Interceptor通过作为输入的InterceptDelegate委托实现针对后续Interceptor或者目标方法的调用,它返回的InterceptDelegate委托对象则体现实现的拦截操作。从这个意义上讲,一个InterceptorDelegate委托不仅仅表示一个单一的Interceptor对象,也可以表示由多一个Interceptor组成的Interceptor Chain。从另一个角度讲,由于一个Interceptor已经实现了针对后续Interceptor的执行,所以一个Interceptor本身就表示一个Interceptor Chain。
三、定义Interceptor类型
虽然Dora.Interception在底层总是使用一个InterceptorDelegate委托表示Interceptor(Chain),为了编程上的便利,我们依然将Interceptor定义成一个类型,我们定义的Interceptor类型只需要采用如下的“约定”即可:
- Interceptor类型无需实现任何的接口,我们只需要定义一个普通的公共实例类型即可。
- Interceptor类型必须具有一个公共构造函数,并且该构造函数的第一个参数的类型必须是InterceptDelegate,后者用于调用后续的Interceptor或者目标方法。
- 除了第一个参数之外,上述这个构造函数可以包含任意的参数定义。
- 拦截功能实现在约定的InvokeAsync的方法中,这是一个返回类型为Task的异步方法,它的第一个参数类型为InvocationContext。
- 除了表示当前执行上下文的参数之外,InvokeAsync可以包含任意的参数定义,但是要求这些参数能够以DI的方式来提供。
- 当前Interceptor是否调用后续的Interceptor或者目标方法,取决于你是否调用构造函数传入的这个InterceptDelegate委托对象。
接下来我们就通过实例演示的方式来简单介绍一下如何遵循上述的这些约定来定义我们的Interceptor类型。如下面的代码片段所示,作为Interceptor类型的FoobarInterceptor具有一个公共的实例构造函数,作为强制要求的第一个参数next表示用于调用后续Interceptor或者目标方法的InterceptDelegate委托对象。除了该参数,我们还定义了额外两个接口类型的参数,这些参数都被保存到对应的字段或者属性上。拦截操作定义在InvokeAsync方法,这个方法的方法名(InvokeAsync)、返回类型(Task)和第一个参数的类型(InvocationContext)都是我们约定的一部分。在这个方法中,我们输出Foo和Bar属性,并最终利用构造函数指定的InterceptDelegate委托对象将调用向后传递。
1 public class FoobarInterceptor
2 {
3 public IFoo Foo { get; }
4 public IBar Bar { get; }
5 private InterceptDelegate _next;
6 public FoobarInterceptor(InterceptDelegate next, IFoo foo, IBar bar)
7 {
8 _next = next;
9 this.Foo = foo;
10 this.Bar = bar;
11 }
12
13 public Task InvokeAsync(InvocationContext context)
14 {
15 Console.WriteLine(this.Foo);
16 Console.WriteLine(this.Bar);
17 return _next(context);
18 }
19 }
20
21
22 public class FoobarAttribute : InterceptorAttribute
23 {
24 public override void Use(IInterceptorChainBuilder builder)
25 {
26 builder.Use<FoobarInterceptor>(this.Order);
27 }
28 }
29
30 public interface IFoo { }
31 public interface IBar { }
32 public class Foo : IFoo { }
33 public class Bar : IBar { }
FoobarAttribute是用于注册FoobarInterceptor的特性,FoobarAttribute派生于InterceptorAttribute这个抽象基类,关于InterceptorAttribute以及相关Interceptor注册的类型我们将在后续的文章中进行介绍。Dora.Interception的一个显著的特征就是与.NET Core的DI实现了无缝集成,具体体现在Interceptor中所需的任何服务都可以直接采用DI的方式来提供,比如FoobarInterceptor的Foo和Bar属性对应的服务实例。如下面的代码片段所示,我们将FoobarAttribute标注到Demo类型的虚方法Invoke上。在Main方法中,我们将IFoo、IBar和Demo对应的服务注册添加到创建的ServiceCollection上,然后调用后者的BuildInterceptableServiceProvider方法创建一个具有“拦截”特性的ServiceProvider。如果由这个ServiceProvider提供的服务类型能够被拦截,它会利用相应的代理类型生成机制动态地生成对应的代理类型,并最终创建出对应的代理实例。
1 class Program
2 {
3 static void Main()
4 {
5 var demo = new ServiceCollection()
6 .AddScoped<IFoo, Foo>()
7 .AddScoped<IBar, Bar>()
8 .AddScoped<Demo, Demo>()
9 .BuildInterceptableServiceProvider()
10 .GetRequiredService<Demo>();
11 demo.Invoke();
12 }
13 }
14
15 public class Demo
16 {
17 [Foobar]
18 public virtual void Invoke()
19 {
20 Console.WriteLine("Demo.Invoke() is invoked");
21 }
22 }
执行上面的代码会在控制台上产生如下的输出结果,我们可以看出应用在Domo.Invoke方法上的FoobarInteceptor被正常执行,它依赖的两个服务类型Foo和Bar正好与服务注册一致。
四、支持方法注入
对于面定义的FoobarInteceptor来说,它依赖的两个服务Foo和Bar实际上是通过构造器注入的方式提供的,实际上我们还具有更加简洁的方案,那就是直接在InvokeAsync方法中对它们进行注入,这也是我们为什么不为Interceptor定义接口的原因。直接采用方法注入会让你的Interceptor变得更加简洁。
1 public class FoobarInterceptor
2 {
3 private InterceptDelegate _next;
4 public FoobarInterceptor(InterceptDelegate next)
5 {
6 _next = next;
7 }
8
9 public Task InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
10 {
11 Console.WriteLine(foo);
12 Console.WriteLine(bar);
13 return _next(context);
14 }
15 }
- android Handler更新UI
- Android 四大组件之Activity生命周期
- Android 简单的代码混淆
- Android Service组件(1)
- Android学习之listview的下拉刷新、上拉载入
- Android Service 系统服务
- Android学习之sqlite与listview
- Android应用开发SharedPreferences存储数据的使用方法
- android 补间动画
- Android Geocoder(位置解析)
- Android服务之AIDL
- SQL语言学习-数据定义语言
- js获取url参数的方法
- JavaScript操作符(关系操作符、相等操作符和条件操作符)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- python之列表推导和生成器表达式
- 二叉树的前中后序遍历
- 【tensorflow2.0】处理结构化数据-titanic生存预测
- 【pandas】pandas中的常见函数
- 走进STL - 哈希表,散装称重么
- 第八届蓝桥杯省赛javaB组题目解析
- 拥抱STL - 类/结构体元素查询与排序
- 【tensorflow2.0】处理图片数据-cifar2分类
- 操作系统实验多线程编程中的读者优先和写者优先
- 【python】使用csv库以字典格式读写csv文件
- 基于TypeScript封装Axios笔记(八)
- springmvc之HttpMessageConverter<T>
- django-模板之静态文件加载(十四)
- springmvc之使用JstlView
- django-模板之include标签(十五)