首页 > 其他分享 >Dora拦截器详解

Dora拦截器详解

时间:2022-08-27 15:15:38浏览次数:94  
标签:拦截器 Interception 详解 Dora 注册 方法 public

大纲

1. Quick Start

Dora拦截器,为.NET Core量身定制的AOP框架。

我们使用“缓存”这个应用场景来演示如何使用Dora:我们创建一个缓存拦截器,并将其应用到某个方法上。缓存拦截器会将目标方法的返回值缓存起来。在缓存过期之前,提供相同参数列表的方法调用会直接返回缓存的数据,而无需执行目标方法。

1.1 Nuget包

Dora.Interception (=v3.0.0)

1.2 定义拦截器

作为Dora.Interception区别于其他AOP框架的最大特性,我们注册的拦截器类型无需实现某个预定义的接口,因为我们采用基于“约定”的拦截器定义方式。

public class CacheInterceptor
{
    private readonly ConcurrentDictionary<object, object> _cache;

    public CacheInterceptor()
    {
        _cache = new ConcurrentDictionary<object, object>();
    }

    public async Task InvokeAsync(InvocationContext context)
    {
        var key = new Cachekey(context.Method, context.Arguments);
        if (_cache.TryGetValue(key, out object value))
        {
            context.ReturnValue = value;
        }
        else
        {
            await context.ProceedAsync();
            _cache.TryAdd(key, context.ReturnValue);
        }
    }
}

按照约定,拦截器类型只需要定义成一个普通的“公共、实例”类型即可。拦截操作需要定义在约定的InvokeAsync方法中,该方法的返回类型为Task,并且包含一个InvocationContext类型的参数。

public class Cachekey
{
    public MethodBase Method { get; }
    public object[] InputArguments { get; }
    public Cachekey(MethodBase method, object[] arguments)
    {
        Method = method;
        InputArguments = arguments;
    }
    public override bool Equals(object obj)
    {
        if (!(obj is Cachekey another))
        {
            return false;
        }
        if (!Method.Equals(another.Method))
        {
            return false;
        }
        for (int index = 0; index < InputArguments.Length; index++)
        {
            var argument1 = InputArguments[index];
            var argument2 = another.InputArguments[index];
            if (argument1 == null && argument2 == null)
            {
                continue;
            }
            if (argument1 == null || argument2 == null)
            {
                return false;
            }
            if (!argument2.Equals(argument2))
            {
                return false;
            }
        }
        return true;
    }
    public override int GetHashCode()
    {
        int hashCode = Method.GetHashCode();
        foreach (var argument in InputArguments)
        {
            hashCode ^= argument.GetHashCode();
        }
        return hashCode;
    }
}

1.3 注册拦截器

在方法上标注特性是我们最常用的拦截器注册方式,为此我们定义CacheAttribute

[AttributeUsage(AttributeTargets.Method)]
public class CacheAttribute : InterceptorAttribute
{
    public override void Use(IInterceptorChainBuilder builder)
    {
        builder.Use<CacheInterceptor>(Order);
    }
}

在重写的Use方法中,我们只需要调用作为参数的IInterceptorChainBuilder对象的Use<TInterceptor>方法将指定的拦截器添加到拦截器链条(同一个方法上可能同时应用多个拦截器)。

1.4 使用拦截器

为了能够很直观地看到针对方法返回值的缓存,我们定义了如下这个表示系统时钟的ISystemClock的服务接口。

public interface ISystemClock
{
    DateTime GetCurrentTime();
}

public class SystemClock : ISystemClock
{
    [Cache]
    public DateTime GetCurrentTime() => DateTime.Now;
}

1.5 依赖注入

public static void Main()
{
    var clock = new ServiceCollection()
         .AddInterception() // 注册Dora.Interception本身的服务
         .AddSingletonInterceptable<ISystemClock, SystemClock>() // 注册可被拦截的服务
         .BuildServiceProvider()
         .GetRequiredService<ISystemClock>();
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(clock.GetCurrentTime());
        Thread.Sleep(1000);
    }
}

运行结果:

result jpg

2. 定义拦截器

对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上。这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用。

2.1 拦截器约定

::: tip 自定义的拦截器需满足以下条件:

  1. 拦截器必须是一个可实例化的类型;
  2. 必须有一个公共构造函数,可以定义任意参数;
  3. 拦截操作定义在一个名为InvokeAsync的方法中,返回类型为Task,其中包含一个名为InvocationContext的参数。
    :::

2.2 InvocationContext

我们为整个拦截器管道定义了一个统一的执行上下文,并将其命名为InvocationContext。我们可以利用InvocationContext对象得到方法调用上下文的相关信息,其中包括两个方法(定义在接口和实现类型),目标对象、参数列表(含输入和输出参数)、返回值(可读写)。

public abstract class InvocationContext
{    
    public abstract MethodInfo Method { get; }
    public MethodInfo TargetMethod { get; }
    public abstract object Target { get; }
    public abstract object[] Arguments { get; }
    public abstract object ReturnValue { get; set; }  
    public abstract IDictionary<string, object> Properties { get; }

    public Task ProceedAsync();
}

属性

  • Method: 注册类型的方法(示例中的ISystemClock.GetCurrentTime()
  • TargetMethod: 目标对象的方法(示例中的SystemClock.GetCurrentTime()
  • Target: 目标对象(示例中的SystemClock
  • Arguments: 参数列表(含输入和输出参数)
  • ReturnValue: 返回值(可读写)
  • Properties: 自定义的属性容器,我们可以利用它来存放任意与当前方法调用上下文相关的信息

方法

  • ProceedAsync: 该方法会调用后续的拦截器。如果是最后一个,则调用目标方法。

2.3 注入依赖服务

拦截器底层是基于.Net Core的依赖注入服务的,所以可以在拦截器定义中注入需要的依赖服务。

public class FoobarInterceptor
{
    private readonly IFoo _foo;
    private readonly IBar _bar;

    public FoobarInterceptor(IFoo foo)
    {
        _foo = foo;
    }

    public async InvokeAsync(InvocationContext context, IBar bar)
    {
        await PreInvokeAsync();
        await context.ProceedAsync();
        await PostInvokeAsync();
    }
}

注入依赖服务共有两种方式:构造函数注入InvokeAsync方法注入

::: warning 构造函数注入和InvokeAsync方法注入的区别
拦截器本质上是一个Singleton服务,我们不应该将Scoped服务注入到它的构造函数中。如果具有针对Scoped服务注入的需要,我们应该将它注入到InvokeAsync方法中。
:::

3. 注册拦截器

Dora.Interception提供了基于特性基于策略两种注册拦截器的方式。

3.1 特性Atrribute

自定义的Attribute只需继承InterceptorAttribute即可。

public abstract class InterceptorAttribute : Attribute, IInterceptorProvider
{
    public int Order { get; set; }
    public bool AllowMultiple { get; }

    public abstract void Use(IInterceptorChainBuilder builder);
}
  • Order: 表示拦截器最终在管道中的位置。
  • AllowMultiple: 表示是否允许多个拦截器标记在同一方法上。默认false,即多个相同拦截器只执行一个。
  • Use: 把拦截器注册到管道调用链中。

扩展方法

public static class InterceptorChainBuilderExtensions
{
    public static IInterceptorChainBuilder Use<TInterceptor>(this IInterceptorChainBuilder builder, 
        int order, params object[] arguments);

    public static IInterceptorChainBuilder Use(this IInterceptorChainBuilder builder, 
        Type interceptorType, int order, params object[] arguments);

    public static IInterceptorChainBuilder Use(this IInterceptorChainBuilder builder, object interceptor, int order);
}
  • order: 指明拦截器调用的位置。
  • arguments: 如果构造函数的参数,依赖注入框架无法提供,则在这里指定。

3.2 策略Policy

策略这种方式的使用示例。

public static void Main(string[] args)
{
    new HostBuilder()
    .UseInterceptableServiceProvider(configure: Configure)
    .Build()
    .Run();
}

public static void Configure(InterceptionBuilder interceptionBuilder)
{
    interceptionBuilder.AddPolicy(policyBuilder => policyBuilder
                            // 注册CacheAttribute拦截器, order=1
                            .For<CacheAttribute>(order: 1, cache => cache
                                // 应用到SystemClock
                                .To<SystemClock>(target => target
                                    // 拦截方法GetCurrentTime
                                    .IncludeMethod(clock => clock.GetCurrentTime())
                                    .IncludeAllMembers() // 包含所有的方法
                                    .IncludeProperty(xxx) // 包含属性
                                    .ExcludeMethod(xxx) // 不拦截方法
                                    .ExcludeProperty(xxx); // 不拦截属性
                                )
                            )
                        )
}

4. 依赖注入

4.1 第一种方式

注册可被拦截的服务。

var services = new ServiceCollection()
         .AddInterception() // 注册Dora.Interception本身的服务
         .AddSingletonInterceptable<ISystemClock, SystemClock>(); // 注册可被拦截的服务

4.2 第二种方式

如果不想改变服务默认的注册方式,可以改用BuildInterceptableServiceProvider方法。

var services = new ServiceCollection()
    .AddSingleton<ISystemClock, SystemClock>()
    .BuildInterceptableServiceProvider();

BuildInterceptableServiceProvider方法内部会调用AddInterception

4.3 第三种方式

Dora.Interception作为第三方依赖注入框架,提供了IServiceProviderFactory接口的实现类,轻松实现与 .Net Core 依赖注入框架的整合。

public sealed class InterceptableServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
    public InterceptableServiceProviderFactory(ServiceProviderOptions options, Action<InterceptionBuilder> configure);

    public IServiceCollection CreateBuilder(IServiceCollection services);
    public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder);
}

可以直接调用UseInterceptableServiceProvider这一扩展方法,完成针对InterceptableServiceProviderFactory的注册。

new HostBuilder()
    .UseInterceptableServiceProvider(configure: null)
    .Build()
    .Run();

参考

  1. AOP框架Dora.Interception 3.0 [1]: 编程体验
  2. AOP框架Dora.Interception 3.0 [2]: 实现原理
  3. AOP框架Dora.Interception 3.0 [3]: 拦截器设计
  4. AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册
  5. AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册

标签:拦截器,Interception,详解,Dora,注册,方法,public
From: https://www.cnblogs.com/renzhsh/p/16630582.html

相关文章

  • Nginx分布式框架详解-基础32-36nginx基础配置实例
    nginx基础配置实例需求分析前面我们已经对Nginx服务器默认配置文件的结构和涉及的基本指令做了详细的阐述。通过这些指令的合理配置,我们就可以让一台Nginx服务器正常......
  • fedora 添加 docker 源
    mv/etc/yum.repos.d/fedora.repo/etc/yum.repos.d/fedora.repo.backupwget-O/etc/yum.repos.d/fedora.repohttp://mirrors.aliyun.com/repo/fedora.repoyummakeca......
  • 【转载】AF_XDP技术详解
    原文信息作者:rexrock出处:https://rexrock.github.io/post/af_xdp1/目录1.用户态程序1.1创建AF_XDP的socket1.2为UMEM申请内存1.3向AF_XDPsocket注册UMEM1.4......
  • Nginx分布式框架详解-基础22-31nginx核心配置文件
    nginx配置文件nginx.conf的文件结构从前面的内容学习中,我们知道Nginx的核心配置文件默认是放在/usr/local/nginx/conf/nginx.conf,本次我们就来学习下nginx.conf的内......
  • SSL单向/双向认证详解
    1、单向认证和双向认证单向认证SSL协议的具体过程①客户端的浏览器向服务器传送客户端SSL协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯......
  • Max_connect_errors – MySQL性能参数详解
    Max_connect_errors–MySQL性能参数详解_wulantian的博客-CSDN博客_max_connect_errors https://blog.csdn.net/wulantian/article/details/9670957max_connect_error......
  • 手绘地图制作实例详解:如何从0到功能丰富的智慧导览系统
    前面比较系统性地说了一些手绘地图制作的基础知识,现在这里以一个实际案例来详述一下如何从0开始,制作功能完善的智慧导览系统。作者:轻轻的烟雾(z281099678)这里讲解的案......
  • ScheduledThreadPoolExecutor详解与总结
    ScheduledThreadPoolExecutor详解简介继承自ThreadPooExecutor,为任务提供延迟或周期执行.使用专门的ScheduledFutureTask来执行周期任务,也可以接收不需要时间调度的任......
  • Kruskal和Prim算法详解
    最小生成树概念(转载)假设一个国家有一些城市,这些城市可以互相连接起来,假设每两个城市之间的道路有很多条,那么一定存在这样的情况,可以用最少的路程连接各个城市。......
  • flutter系列之:构建Widget的上下文环境BuildContext详解
    目录简介BuildContext的本质BuildContext和InheritedWidgetBuildContext的层级关系总结简介我们知道Flutter中有两种Widget,分别是StatelessWidget和StatefulWidget,Statel......