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下的实现.对于下面演示 ...
随机推荐
- Visual Studio 2017 : client version 1.22 is too old
使用Vs2017 编译 eShopOnContainers-ServicesAndWebApps 时,报了错误: Microsoft.DotNet.Docker.CommandLineClientEx ...
- Mockplus设计大赛获奖选手专访 | High音:轻松生活,随心嗨音
"看似低调,实则高调的设计,UI设计是用了功力,主页功能和内容一览无余,方便用户选择,金字黑底,给予用户极好的奢华体验.原来听歌也是一种视觉享受.创新性源于对听歌氛围的把握,大幅的图片,刺激 ...
- PXE+Kickstart 全自动安装部署CentOS7.4
一.简介 1.什么是PXE PXE(preboot execute environment,预启动执行环境)是由Intel公司开发的最新技术,工作于Client/Server的网络模式,支持工作站通过 ...
- 刚实习的自己-php
刚毕业的大学生,可能你的理论知识很丰富,但是你要清楚的是:你缺少实战经验. 正式实习的时候是在下午,老板给了我一个他们几年前开发好的系统(cms),这是一个展示型的网站,也就是发 ...
- Python 项目实践三(Web应用程序)第四篇
接着上节继续学习,本章将建立用户账户 Web应用程序的核心是让任何用户都能够注册账户并能够使用它,不管用户身处何方.在本章中,你将创建一些表单,让用户能够添加主题和条目,以及编辑既有的条目.你还将学习 ...
- 区分javascript中的toString(),toLocaleString(),valueOf()方法
首先我们随意创建一个对象,这很简单,打开FF浏览器的Firebug切换到控制台或者打开webkit浏览器的审查元素功能. 输入以下内容: var obj1=[1,2,3,4,5] var obj2=[ ...
- Mobiscroll的介绍【一款兼容PC和移动设备的滑动插件】
Mobiscroll是一个用于触摸设备的日期和时间选择器,它的使用不会改变HTML5.PhoneGap以及混合应用的原生用户体验.作为一款jQuery滑动选择插件,用户可以自定义主题样式,为自己的移动 ...
- admin
执行顺序 : Admin 执行admin.py,导入models 第一次进来的时候,先创建admin.site对象(如果下次再有引入,不会重新创建) 拿到对象后执行该对象下的register()方法 ...
- 关于 jar 包数据更新的问题
参考: 人乐草心的博文 如果要更新一个 jar 包内文件的一些信息,又不想重新编译,发包,可以如下操作. Extract JAR file unzip 拆包方式 unzip xxx.jar [ -d ...
- python模块:shelve
shelve 1)模块功能:以 key - value 的方式存储数据. 2)写数据 >>> import shelve >>> db = shelve.open( ...