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则很明显,分别表示当前的代理对象和被封装的目标对象,如果采用基于虚方法的代理类型生成方式,两个属性返回同一个对象。
public abstract class InvocationContext
{
public abstract MethodBase Method { get; }
public MethodBase TargetMethod { get; }
public abstract object Proxy { get; }
public abstract object Target { get; }
public abstract object[] Arguments { get; }
public abstract object ReturnValue { get; set; }
}
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对象。
public delegate Task InterceptDelegate(InvocationContext context);
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委托对象将调用向后传递。
public class FoobarInterceptor
{
public IFoo Foo { get; }
public IBar Bar { get; }
private InterceptDelegate _next;
public FoobarInterceptor(InterceptDelegate next, IFoo foo, IBar bar)
{
_next = next;
this.Foo = foo;
this.Bar = bar;
} public Task InvokeAsync(InvocationContext context)
{
Console.WriteLine(this.Foo);
Console.WriteLine(this.Bar);
return _next(context);
}
} public class FoobarAttribute : InterceptorAttribute
{
public override void Use(IInterceptorChainBuilder builder)
{
builder.Use<FoobarInterceptor>(this.Order);
}
} public interface IFoo { }
public interface IBar { }
public class Foo : IFoo { }
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提供的服务类型能够被拦截,它会利用相应的代理类型生成机制动态地生成对应的代理类型,并最终创建出对应的代理实例。
class Program
{
static void Main()
{
var demo = new ServiceCollection()
.AddScoped<IFoo, Foo>()
.AddScoped<IBar, Bar>()
.AddScoped<Demo, Demo>()
.BuildInterceptableServiceProvider()
.GetRequiredService<Demo>();
demo.Invoke();
}
} public class Demo
{
[Foobar]
public virtual void Invoke()
{
Console.WriteLine("Demo.Invoke() is invoked");
}
}
执行上面的代码会在控制台上产生如下的输出结果,我们可以看出应用在Domo.Invoke方法上的FoobarInteceptor被正常执行,它依赖的两个服务类型Foo和Bar正好与服务注册一致。
四、支持方法注入
对于面定义的FoobarInteceptor来说,它依赖的两个服务Foo和Bar实际上是通过构造器注入的方式提供的,实际上我们还具有更加简洁的方案,那就是直接在InvokeAsync方法中对它们进行注入,这也是我们为什么不为Interceptor定义接口的原因。直接采用方法注入会让你的Interceptor变得更加简洁。
public class FoobarInterceptor
{
private InterceptDelegate _next;
public FoobarInterceptor(InterceptDelegate next)
{
_next = next;
} public Task InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
{
Console.WriteLine(foo);
Console.WriteLine(bar);
return _next(context);
}
}
Dora.Interception, 为.NET Core度身打造的AOP框架 [1]:全新的版本
Dora.Interception, 为.NET Core度身打造的AOP框架 [2]:不一样的Interceptor定义方式
Dora.Interception, 为.NET Core度身打造的AOP框架 [3]:Interceptor的注册
Dora.Interception, 为.NET Core度身打造的AOP框架 [4]:演示几个典型应用
Dora.Interception, 一个为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式的更多相关文章
- Dora.Interception, 一个为.NET Core度身打造的AOP框架[3]:Interceptor的注册
在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [2]:以约定的方式定义拦截器
上一篇<更加简练的编程体验>提供了最新版本的Dora.Interception代码的AOP编程体验,接下来我们会这AOP框架的编程模式进行详细介绍,本篇文章着重关注的是拦截器的定义.采用“ ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式
在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...
- Dora.Interception: 一个为.NET Core度身定制的AOP框架
多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用“注入”的方式将 ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验
很久之前开发了一个名为Dora.Interception的开源AOP框架(github地址:https://github.com/jiangjinnan/Dora,如果你觉得这个这框架还有那么一点价值 ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [5]:轻松地实现与其他AOP框架的整合
这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL E ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [4]:与依赖注入框架的无缝集成
Dora.Interception最初的定位就是专门针对.NET Core的AOP框架,所以在整个迭代过程中我大部分是在做减法.对于.NET Core程序开发来说,依赖注入已经成为无处不在并且“深入骨 ...
- Dora.Interception,为.NET Core度身打造的AOP框架:全新的版本
Dora.Interception 1.0(Github地址:可以访问GitHub地址:https://github.com/jiangjinnan/Dora)推出有一段时间了,最近花了点时间将它升级 ...
- Dora.Interception, 为.NET Core度身打造的AOP框架[4]:演示几个典型应用
为了帮助大家更深刻地认识Dora.Interception,并更好地将它应用到你的项目中,我们通过如下几个简单的实例来演示几个常见的AOP应用在Dora.Interception下的实现.对于下面演示 ...
随机推荐
- 开源框架SmartImageView的使用
1.SmartImageView为一个网络图片框架,可以将一个网络URL的图片展示在UI上 2.SmartImageView使用 @1Github上下载源代码 @2拷贝原码到工程src下 @3布局配置 ...
- C#设计模式之二十二备忘录模式(Memeto Pattern)【行为型】
一.引言 今天我们开始讲"行为型"设计模式的第十个模式,该模式是[备忘录模式],英文名称是:Memento Pattern.按老规矩,先从名称上来看看这个模式,个人的最初理解就 ...
- ElasticSearch 学习记录之ES如何操作Lucene段
近实时搜索 提交(Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据.但是每次提交的一个新的段都fsync 这样操作代价过大.可以使用 ...
- 比较日期大小以及获取select选中的option的value
原生JavaScript如何获取select选中的value // 1. 拿到select对象 const selectObject = document.getElementById('test') ...
- 记录python学习过程中的一些小心得
1.python中一切皆对象,内置数据结构也是对象.处理一个对象就是利用它带有的方法和属性,对该对象进行处理,一步步达到我们想要的结果. 2.编程时,先构思好我们处理的对象是什么,具有哪些属性和方法, ...
- Git详解之七:自定义Git
自定义 Git 到目前为止,我阐述了 Git 基本的运作机制和使用方式,介绍了 Git 提供的许多工具来帮助你简单且有效地使用它. 在本章,我将会介绍 Git 的一些重要的配置方法和钩子机制以满足自定 ...
- UWP 手绘视频创作工具技术分享系列 - 文字的解析和绘制
本篇作为技术分享系列的第二篇,详细讲一下文字的解析和绘制,这部分功能的研究和最终实现由团队共同完成,目前还在寻找更理想的实现方式. 首先看一下文字绘制在手绘视频中的应用场景 文字是手绘视频中很重要的表 ...
- Head First设计模式之访问者模式
一.定义 定义:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作. 访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操 ...
- Struts2内部执行过程
首先是Struts2的流程图. 一.当有一个请求的时候.执行以下流程. 1 客户端初始化一个指向Servlet容器的请求: 2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做Act ...
- 记一次Hbase查询速度优化经历
项目背景: 在这次影像系统中,我们利用大数据平台做的是文件(图片.视频等)批次的增删改查,每个批次都包含多个文件,上传完成以后要添加文件索引(文件信息及批次信息),由于在Hbase存储的过程中,每个文 ...