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

很久之前开发了一个名为Dora.Interception的开源AOP框架(github地址:https://github.com/jiangjinnan/Dora,如果你觉得这个这框架还有那么一点价值,请不吝多点一颗星),最近对它作了一些改进(包括编程模式和性能,目前最新版本2.1.4)。一直以来我对软件设计秉承的一个理念就是:好的设计应该是简单的设计。和其他AOP框架相比,虽然Dora.Interception提供的编程模式已经显得足够简单,但是我觉得还应该再简单点,再简单点。这个新版本对拦截器的定义和应用提供了更加简单的定义方式,同时对扩展性方法作了较大的改进,接下来我们通过一个简单实例来体验一下。源代码从这里下载。

一、定义拦截器类型

Dora.Interception中的拦截器类型不需要实现任何的接口或者继承任何的基类,因为我们采用“基于约定”的设计方案。由于Dora.Interception是建立在.NET Core的依赖注入框架之上,所以我们可以将任意依赖的服务直接注入到定义的截器类型中。接下来我们将定义一个名为CacheInterceptor的拦截器来实现针对方法返回值的缓存。由于缓存的内容是某个方法的返回值,所以我们将方法和参数列表作为缓存的Key,这个Key由如下这个CacheKey来表示(完整定义请参阅源代码)。

public class CacheKey
{
    public MethodBase Method { get; }
    public object[] InputArguments { get; }

    public CacheKey(MethodBase method, object[] arguments)
    {
        this.Method = method;
        this.InputArguments = arguments;
    }
    public override bool Equals(object obj);public override int GetHashCode();
}

我们直接利用ASP.NET Core基于内存的缓存框架来对方法返回值实施缓存,所以我们直接将IMemoryCache服务和对应的Options以如下的方式注入到CacheInterceptor的构造函数中。具体的拦截操作实现在按照约定定义的InvokeAsync方法中,我们可以利用作为输入参数的InvocationContext 对象得到当前方法调用的所有上下文信息,也可以直接通过它的ReturnValue设置方法的返回值。在如下所示的代码片段中,我们正是利用这个InvocationContext对象得到表示当前调用方法的MethodInfo对象和输入参数,并以它们创建出CacheKey对象来操作缓存。

public class CacheInterceptor
{
    private readonly IMemoryCache _cache;
    private readonly MemoryCacheEntryOptions _options;
    public CacheInterceptor(IMemoryCache cache, IOptions<MemoryCacheEntryOptions> optionsAccessor)
    {
        _cache = cache;
        _options = optionsAccessor.Value;
    }

    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.Set(key, context.ReturnValue, _options);
        }
    }
}

对于一个拦截器对象来说,当调用被其拦截之后,需要由它自己来决定是否需要继续后续的调用,在新的版本中,我们采用直接调用InvocationContext的ProceedAsync方法的方式来达到这个目的。上面这个CacheInterceptor类型采用构造器注入的方式来注入依赖的服务,实际上我们还具有更加简单的方案,那就是采用如下的方式直接将依赖服务注入到InvokeAsync方法中。

public class CacheInterceptor
{
    public async Task InvokeAsync(InvocationContext context, 

IMemoryCache cache, IOptions<MemoryCacheEntryOptions> optionsAccessor

)
    {
        var key = new CacheKey(context.Method, context.Arguments);
        if (cache.TryGetValue(key, out object value))
        {
            context.ReturnValue = value;
        }
        else
        {
            await context.ProceedAsync();
            cache.Set(key, context.ReturnValue, optionsAccessor.Value);
        }
    }
}

二、应用拦截器

所谓的Interceptor应用就是如何将Interceptor应用到定义在某个类型上的某个方法的过程。Dora.Interception默认提供了多种注册方式,最为常用的莫过于采用Attribute标注的方式来注册Interceptor。如果需要采用这种方式来注册CacheInterceptor,我们需要采用如下的方式为Interceptor类型定义对应的CacheReturnValueAttribute 类型。CacheReturnValueAttribute 派生于抽象类InterceptorAttribute,在重写的Use方法中,它调用作为参数的IInterceptorChainBuilder 对象的Use方法将CacheInterceptor添加到Interceptor管道中,传入的参数(Order)代表Interceptor在管道中的位置。

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

Dora.Interception刻意地将Interceptor和对应的Attribute区分对象,因为我们认为后者仅仅是Interceptor的一种“注册方式‘’而已。如果我们希望将二者合一,我们可以采用如下的定义方式。

public class CacheInterceptorAttribute : InterceptorAttribute
{
    public async Task InvokeAsync(InvocationContext context, IMemoryCache cache, IOptions<MemoryCacheEntryOptions> optionsAccessor)
    {
        var key = new CacheKey(context.Method, context.Arguments);
        if (cache.TryGetValue(key, out object value))
        {
            context.ReturnValue = value;
        }
        else
        {
            await context.ProceedAsync();
            cache.Set(key, context.ReturnValue, optionsAccessor.Value);
        }
    }
    public override void Use(IInterceptorChainBuilder builder) => builder.Use(this, Order);
}

为了演示CacheInterceptor针对目标返回值的缓存,我们定义了如下这个标识“系统时钟”的ISystemClock服务,它的GetCurrentTime方法返回当前的时间。为了验证基于参数的缓存,我们为该方法定义了一个表示事件类型(Local或者UTC)的参数。上面定义的CacheReturnValueAttribute标注在实现类型的GetCurrentTime方法上。很多AOP框架都支持将Interceptor直接应用到服务接口上,但我个人觉得这是不对的,因为接口表示的是双边契约,Interceptor体现的是单边的行为,所以Interceptor是不应该应用到接口上。

public interface ISystemClock
{
    DateTime GetCurrentTime(DateTimeKind dateTimeKind);
}

public class DefaultSystemClock : ISystemClock
{
    [CacheReturnValue]
    public DateTime GetCurrentTime(DateTimeKind dateTimeKind)
    => dateTimeKind == DateTimeKind.Utc
            ? DateTime.UtcNow
            : DateTime.Now;
}

三、你的代码不需要任何改变

将Dora.Interception引入你的应用完全不会影响你现有的代码,比如在消费ISystemClock服务的时候完全不用考虑CacheInterceptor的存在。如下所示的就是典型地在Controller中以注入形式消费服务的编程模式。

public class HomeController: Controller
{
    private readonly ISystemClock _clock;
    public HomeController(

ISystemClock clock

)  => _clock = clock;

    [HttpGet("/")]
    public async Task Index()
    {
        async Task<string[]> GetTimesAsync()
        {
            var times = new string[6];
            for (int index = 0; index < 3; index++)
            {
                times[index] = $"Local: {_clock.GetCurrentTime(DateTimeKind.Local)}";
                await Task.Delay(1000);
            }

            for (int index = 3; index < 6; index++)
            {
                times[index] = $"UTC: {_clock.GetCurrentTime(DateTimeKind.Utc)}";
                await Task.Delay(1000);
            }
            return times;
        }

        var currentTimes = await GetTimesAsync();
        var list = string.Join("", currentTimes.Select(it => $"<li>{it}</li>"));
        Response.ContentType = "text/html";
        await Response.WriteAsync(
            @"<html>
                <body>
                    <ul>" + list +
                    @"</ul>
                </body>
            </html>");   
    }
}

我们唯一需要做的就是在注册Startup类型的ConfigureServices方法中调用IServiceCollection的扩展方法BuildInterceptableServiceProvider方法创建并返回一个IServiceProvider,后者能够帮助我们创建出能够被拦截的服务实例。BuildInterceptableServiceProvider方法提供的是一个InterceptableServiceProvider对象,InterceptableServiceProvider与目前.NET Core DI框架基本上是一致的,我仅仅对它作了一些微小的改动。

public class Startup
{
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services
            .AddSingleton<ISystemClock, DefaultSystemClock>()
            .AddMemoryCache()
            .AddMvc();
        return services.BuildInterceptableServiceProvider();
    }
    public void Configure(IApplicationBuilder app) => app.UseDeveloperExceptionPage().UseMvc();
}

如果运行上面这个简单的ASP.NET Core MVC应用,浏览器将会呈现出如下所示的输出结果。由于SystemClock的GetCurrentTime方法的返回值被缓存了,所以针对相同参数返回的时间是相同的。

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

原文地址:https://www.cnblogs.com/linybo/p/10052977.html

时间: 2024-11-01 16:40:58

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

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

为了帮助大家更深刻地认识Dora.Interception,并更好地将它应用到你的项目中,我们通过如下几个简单的实例来演示几个常见的AOP应用在Dora.Interception下的实现.对于下面演示的实例,它们仅仅是具有指导性质的应用,所以我会尽可能地简化,如果大家需要将相应的应用场景移植到具体的项目开发中,需要做更多的优化.源代码从这里下载. 目录一.对输入参数的格式化二.对参数的自动化验证三.对方法的返回值进行自动缓存 一.对输入参数的格式化 我们有一些方法对输入参数在格式上由一些要求,但

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

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

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

Dora.Interception最初的定位就是专门针对.NET Core的AOP框架,所以在整个迭代过程中我大部分是在做减法.对于.NET Core程序开发来说,依赖注入已经成为无处不在并且“深入骨髓”的东西,不论是在进行业务应用的开发,还是进行基础组件的开发,依赖注入是实现“松耦合”最为理想的方式(没有之一).对于绝大部分AOP框架来说,它们最终都会体现为创建一个能够拦截的“代理对象”来实现对方法调用的拦截,但是.NET Core中针对服务实例的提供完全由通过IServiceProvider

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

这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL Emit实现方式,并且对IL指令进行了深度的优化,但是如果我们真的具有更好的选择,我们可以通过简单的扩展完成对底层拦截机制改变. 一.IInterceptingProxyFactory 对于Dora.Interception来说,方法调用之所有能够被拦截的根源在于我们改变了服务实例的提供方式,原来的对

NET Core度身定制的AOP框架

NET Core度身定制的AOP框架 多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用"注入"的方式将它们放到需要的程序中.我们知道AOP是解决这类问题最理想的方案.为此,我自己写了一个AOP框架,该框架被命名为Dora.Interception.Dora.Interception已经在GitHub上开源,如果有兴趣的朋友想下载源代码或者阅读相关文档,

AOP框架Dora.Interception 3.0 [1]: 编程体验

.NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它进行大范围的重构甚至重新设计.这次重构大部分是在做减法,其目的在于使设计和使用更加简单和灵活,接下来我们就来体验一下在一个ASP.NET Core应用程序下如何使用Dora.Interception. 源代码下载实例1(Console)实例2(ASP.NET Core MVC + 注册可拦截服务)实

AOP框架Dora.Interception 3.0 [3]: 拦截器设计

对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上.这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用.从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的模式来设计拦截器. 一.InvocationContext 我们为整个拦截器管道定义了一个统一的执行上下文,并将其命名为InvocationContext.如下面的代码片段所示,我们可以利用InvocationContext对象得到方法调用上下文的相关信息,其中包括两个方法(定义在接口和实现类型),

AOP框架Dora.Interception 3.0 [4]: 基于特性的拦截器注册

按照单一职责的原则,拦截器只负责需要的拦截操作的执行,至于它采用何种方式应用到目标方法上,以及它在整个拦截器管道中的位置则属于“拦截器注册”的范畴.Dora.Interception提供了几种典型的注册方法,用户也可以根据自己的需求实现自己的注册方式. 一.IInterceptorProvider 一般来说,每一个拦截器类型都对应着一个IInterceptorProvider实现类型,后者利用其Use方法负责将前者放置到拦截器管道指定的位置.如下面的代码所示,IInterceptorProvid

coolpad手机量身打造的刷机工具--下载助手_V2.2.4(Mini_AD_Coolpad)

下载助手_V2.2.4(Mini_AD_Coolpad)是下载助手_V2.2.4系列软件中针对coolpad手机量身打造的刷机工具. 下载助手_V2.2.4(DownloadAssistant_V2.2.4)是一系列高效.安全.方便.无忧的ROM版本刷机工具,由墨科通讯推出. 先上一张图: 下载助手_V2.2.4(Mini_AD_Coolpad)是该系列软件中专门用于coolpad手机刷机的工具.下载地址:http://www.morecomtech.com/community/tool/dlo