相较于社区其他主流的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定义方式的更多相关文章

  1. Dora.Interception, 一个为.NET Core度身打造的AOP框架[3]:Interceptor的注册

    在<不一样的Interceptor>中我们着重介绍了Dora.Interception中最为核心的对象Interceptor,以及定义Interceptor类型的一些约定.由于Interc ...

  2. Dora.Interception,为.NET Core度身打造的AOP框架 [2]:以约定的方式定义拦截器

    上一篇<更加简练的编程体验>提供了最新版本的Dora.Interception代码的AOP编程体验,接下来我们会这AOP框架的编程模式进行详细介绍,本篇文章着重关注的是拦截器的定义.采用“ ...

  3. Dora.Interception,为.NET Core度身打造的AOP框架 [3]:多样化拦截器应用方式

    在<以约定的方式定义拦截器>中,我们通过对拦截器的介绍了Dora.Interception的两种拦截机制,即针对接口的“实例拦截”针对虚方法的“类型拦截”.我们介绍了拦截器的本质以及基于约 ...

  4. Dora.Interception: 一个为.NET Core度身定制的AOP框架

    多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用“注入”的方式将 ...

  5. Dora.Interception,为.NET Core度身打造的AOP框架 [1]:更加简练的编程体验

    很久之前开发了一个名为Dora.Interception的开源AOP框架(github地址:https://github.com/jiangjinnan/Dora,如果你觉得这个这框架还有那么一点价值 ...

  6. Dora.Interception,为.NET Core度身打造的AOP框架 [5]:轻松地实现与其他AOP框架的整合

    这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL E ...

  7. Dora.Interception,为.NET Core度身打造的AOP框架 [4]:与依赖注入框架的无缝集成

    Dora.Interception最初的定位就是专门针对.NET Core的AOP框架,所以在整个迭代过程中我大部分是在做减法.对于.NET Core程序开发来说,依赖注入已经成为无处不在并且“深入骨 ...

  8. Dora.Interception,为.NET Core度身打造的AOP框架:全新的版本

    Dora.Interception 1.0(Github地址:可以访问GitHub地址:https://github.com/jiangjinnan/Dora)推出有一段时间了,最近花了点时间将它升级 ...

  9. Dora.Interception, 为.NET Core度身打造的AOP框架[4]:演示几个典型应用

    为了帮助大家更深刻地认识Dora.Interception,并更好地将它应用到你的项目中,我们通过如下几个简单的实例来演示几个常见的AOP应用在Dora.Interception下的实现.对于下面演示 ...

随机推荐

  1. AntData.ORM框架 之 读写分离

    环境准备 准备2台机器配置好Master Slaver模式 我是用vmware 2台虚拟机配置的.有需要请联系. Master:192.168.11.130 Slaver:192.168.11.133 ...

  2. fdisk 命令详解

    fdisk  作用: 查看磁盘实体使用情况,也可对硬盘分区. 选项:  -b 分区大小 -l  列出指定的外围设备的分区表状况 -s 分区编号, 将指定的分区大小输出到标准输出上, 单位为区块 -u ...

  3. 浅析Node.js的Event Loop

    目录 浅析Node.js的Event Loop 引出问题 Node.js的基本架构 Libuv Event Loop Event Loop Phases Overview Poll Phase The ...

  4. 学习Object.assign()

    Object.assign()用于将所有可枚举的值从一个或多个源对象复制到目标对象.它将返回目标对象. 语法 Object.assign(target, ...source); var obj = { ...

  5. DEDECMS最新5.7版在Windows下的Memcache安装

    一,织梦后台后台设置进入系统后台,在[系统基本参数]下面的"性能选项"卡当中,关于memcache进行如下配置: cfg_memcache_enable : 是否启用memcach ...

  6. Ubuntu配置Django+ Apache2+ mysql

    # 我的Ubuntu上自带的python3.5,所以安装一下 python3.6sudo add-apt-repository ppa:jonathonf/python-3.6sudo apt-get ...

  7. Disruptor并发框架 (二)核心概念场景分析

    核心术语 RingBuffer(容器): 被看作Disruptor最主要的组件,然而从3.0开始RingBuffer仅仅负责存储和更新在Disruptor中流通的数据.对一些特殊的使用场景能够被用户( ...

  8. 伽罗瓦域(有限域)GFq^12上元素的1→2→4→12塔式扩张(1)------第一次扩张

    伽罗瓦域是抽象代数下的域论分支中的内容,这部分想必很多人都比较熟悉,此处不再赘述. 最近,国密算法中的SM2和SM9已经成为国际标准,其中SM9算法在椭圆曲线离散对数难题的基础上,添加了若干个双线性配 ...

  9. 讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上)

    前言: 好久没写文章了,最近先是重构IT恋.又重写IT恋中. Sagit框架也不断的更新,调整,现在感觉已完美了了相当的多. 今天不写教程,先简单分享一下技术内容. 1:见Block必有:#defin ...

  10. VMware_ubuntu设置共享文件夹

    1. 点击安装VMware tools 2.将/media/vmtool的压缩包复制到/home/pc/vm_tool下,应为原路径在root权限下竟然也是只读的,并且无法更改. 3.进入/home/ ...