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

上一篇《更加简练的编程体验》提供了最新版本的Dora.Interception代码的AOP编程体验,接下来我们会这AOP框架的编程模式进行详细介绍,本篇文章着重关注的是拦截器的定义。采用“基于约定”的Interceptor定义方式是Dora.Interception区别于其他AOP框架的一个显著特征,要了解拦截器的编程约定,就得先来了解一下Dora.Interception中针对方法调用的拦截是如何实现的。

一、针对实例的拦截

总地来说,Dora.Interception针对方法调用的拦截机制分为两种类型,我将它称为“针对实例的拦截”和“针对类型”的拦截。针对实例的拦截应用于针对接口的方法调用,其原理如下所示:类型Foobar实现了接口IFoobar,如果需要拦截以接口的方式调用Foobar对象的某个方法,我们可以动态生成另一个用来封装Foobar对象的FoobarProxy类型,FoobarProxy同样实现IFoobar接口,我们在实现的方法中实现对Interceptor链的调用。我们最终将原始提供的Foobar对象封装成FoobarProxy对象,那么针对Foobar的方法调用将转换成针对FoobarProxy对象的调用,拦截得以实现。

二、针对类型的拦截

如果Foobar并未实现任何接口,或者针对它的调用并非以接口的方式进行,那么我们只能采用“针对类型的拦截”,其原理如下:我们动态创建Foobar的派生类型FoobarProxy,并重写其需要被拦截的虚方法来实现对Interceptor链的调用。我们最终创建FoobarProxy对象来替换掉原始的Foobar对象,那么针对Foobar的方法调用将转换成针对FoobarProxy对象的调用,拦截得以实现。

由于这种拦截方式会直接创建代理对象,无法实现针对目标对象的封装,当我们进行DI服务注册的时候,只能指定注册服务的实现类型,不能指定一个现有的Singleton实例或者提供一个创建实例的Factory。

三、从两个Delegate说起

要理解Dora.Interception的设计,先得从如下这两个特殊的Delegate类型(InterceptDelegateInterceptorDelegate)说起。InterceptDelegate代表针对方法的拦截操作,作为输入参数的InvocationContext提供了当前方法调用的所有上下文信息,返回类型被设置为Task意味着Dora.Interception提供了针对基于Task的异步编程的支持。

public delegate Task InterceptDelegate(InvocationContext context);
public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);
public abstract class InvocationContext
{
    public abstract object[] Arguments { get; }
    public abstract MethodBase Method { get; }
    public InterceptDelegate Next { get;  }
    public abstract object Proxy { get; }
    public abstract object ReturnValue { get; set; }
    public abstract object Target { get; }
    public MethodBase TargetMethod { get; }
    public abstract IDictionary<string, object> ExtendedProperties { get; }

    public Task ProceedAsync();
}

InterceptDelegate表示的是“拦截操作”,即表示作用于InvocationContext上下文上的一个Task,但它并不能表示一个拦截器对象。原因很简单,因为注册到同一个方法上的多个拦截器对象会构成一个链条,最终决定是否调用后一个拦截器或者目标方法(对于链条尾部的Interceptor)是由当前拦截器决定的,所以如果将Interceptor也表示成委托对象,它的输入应该是一个InterceptDelegate对象,表示针对后一个拦截器或者目标方法的调用,它返回的同样也是一个InterceptDelegate对象,表示将自身纳入拦截器链之后,新的拦截器链条(包括调用目标方法)所执行的操作。

所以一个Interceptor在Dora.Interception中应该表示成一个Func<InterceptDelegate, InterceptDelegate>对象,这与ASP.NET Core的中间件管道其实是一回事。简单起见,我们为它专门定义了一个委托类型InterceptorDelegate。

四、将一个对象转换成Interceptor

虽然Dora.Interception总是将Interceptor对象表示成上面介绍的InterceptorDelegate类型的委托,但是为了更好的编程体验,我们可以选择采用POCO类型的方法来定义Interceptor。为了提供更好的灵活性,能够在方法中动态注入任意依赖服务,我们并不打算为这样的Interceptor类型定义一个接口。接口是一个契约,同时也是一个限制。如果类型实现某个接口,意味着必需按照规定的声明实现其方法,针对方法的服务注入将无法实现,所以Dora.Interception采用“基于约定”的方式来定义Interceptor类型。具体的约定如下

  • Interceptor只需要定义一个普通的实例类型即可。
  • Interceptor类型必须具有一个公共构造函数,它可以包含任意的参数,并支持构造器注入
  • 拦截功能实现在约定的InvokeAsync的方法中,这是一个返回类型为Task的异步方法,它的第一个参数类型为InvocationContext
  • 除了表示当前执行上下文的参数之外, 任何可以注入的服务于对象都可以定义成InvokeAsync方法的参数。
  • 当前Interceptor针对后续的Interceptor或者目标方法的调用通过调用InvocationContext的ProceedAsync方法来实现。

如下所示的就是一个典型的Interceptor,它提供了针对构造函数和方法的注入。

public class FoobarInterceptor
{
    public IFoo Foo { get; }
    public string Baz { get; }  
    public FoobarInterceptor(IFoo foo, string baz)
    {
        Foo = foo;
        Baz = baz;
    }

    public async Task InvokeAsync(InvocationContext context, IBar bar)
    {
        await Foo.DoSomethingAsync();
        await bar.DoSomethingAsync();
        await context.ProceedAsync();
    }
}

[1]:更加简练的编程体验 [2]:基于约定的拦截器定义方式 [3]:多样性的拦截器应用方式 [4]:与依赖注入框架的深度整合 [5]:对拦截机制的灵活定制

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET Core中的依赖注入(3): 服务的注册与提供

在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个...

2407
来自专栏黑泽君的专栏

c语言基础学习11_项目实战:IDE(集成开发环境)

============================================================================= ==...

1922
来自专栏向治洪

android classloader双亲委托模式

概述 ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ;...

2488
来自专栏余林丰

MyBatis之简单了解Plugin

MyBatis的Configuration配置中有一个Plugin配置,根据其名可以解释为“插件”,这个插件实质可以理解为“拦截器”。“拦截器”这个名词不陌生,...

2229
来自专栏木子昭的博客

<数据库>高性能Redis快速入门 | (附Redis常用命令)Redis存储数据 的 五种数据结构

Redis是一个非关系型数据库,也是一个内存数据库(确切一点,可以把它看做内存数据结构服务器, 设计极其精简,如果说在mongo里面还能看到表的影子"集合(c...

3949
来自专栏H2Cloud

FFLIB之FFLUA——C++嵌入Lua&扩展Lua利器

摘要: 在使用C++做服务器开发中,经常会使用到脚本技术,Lua是最优秀的嵌入式脚本之一。Lua的轻量、小巧、概念之简单,都使他变得越来越受欢迎。本人也使用过p...

6577
来自专栏不止是前端

深入Node

35913
来自专栏DOTNET

【翻译】MongoDB指南/引言

【原文地址】https://docs.mongodb.com/manual/ 引言 MongoDB是一种开源文档型数据库,它具有高性能,高可用性,自动扩展性 1...

2566
来自专栏java技术学习之道

JVM初探 -JVM内存模型

1584
来自专栏蓝天

RPC的实现

RPC全称为Remote Procedure Call,即远过程调用。如果没有RPC,那么跨机器间的进程通讯通常得采用消息,这会降低开发效率,也会增加网络...

1963

扫码关注云+社区

领取腾讯云代金券