很久之前开发了一个名为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[];
for (int index = ; index < ; index++)
{
times[index] = $"Local: {_clock.GetCurrentTime(DateTimeKind.Local)}";
await Task.Delay();
} for (int index = ; index < ; index++)
{
times[index] = $"UTC: {_clock.GetCurrentTime(DateTimeKind.Utc)}";
await Task.Delay();
}
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]:对拦截机制的灵活定制

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

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

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

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

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

  3. Dora.Interception, 为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式

    相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式.我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口, ...

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

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

  5. Dora.Interception, 一个为.NET Core度身打造的AOP框架:不一样的Interceptor定义方式

    相较于社区其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的编程方式.我们并没有为Interceptor定义一个接口,正是因为不需要实现一个预定义的接口, ...

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

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

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

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

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

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

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

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

随机推荐

  1. JS Bootstrap-DateRangePicker 如何设置默认值为空

    DateRangePicker是一款时间范围选择器,界面良好,非常适合短时间范围选择的插件,具体源码可以在http://www.daterangepicker.com/找到 ,但是目前使用中,感觉功能 ...

  2. JAVAFX之tableview界面实时刷新导致的内存溢出(自己挖的坑,爬着也要出来啊0.0)

    这几天遇到了一个问题,不幸开发的一个cs架构的工具,客户端开启后,内存一直在缓慢增长最终导致进程卡死,花了4天时间,终于爬出来了... 客户端通过timer定时器每30秒查询一次数据库以及一些业务逻辑 ...

  3. Velocity 模板引擎的应用

    springboot三层机构,还有数据映射待实体.肯定需要一套模板引擎呀.那不然还手写不成. 根据我们的实际业务需求,我添加了一套数据库反向生成实体类的模板,用的是Velocity 的引擎. 不多说直 ...

  4. 从大数据技术变迁猜一猜AI人工智能的发展

    目前大数据已经成为了各家互联网公司的核心资产和竞争力了,其实不仅是互联网公司,包括传统企业也拥有大量的数据,也想把这些数据发挥出作用.在这种环境下,大数据技术的重要性和火爆程度相信没有人去怀疑. 而A ...

  5. java jdk 8反编译工具JD-GUI、procyon-decompiler、luyten、crf下载使用简介

    本文对常用的反编译工具进行简单介绍 JD-GUI.procyon-decompiler.luyten.crf   反编译工具分类 JD-GUI JDK7以及之前可以使用   JD-GUI,如果版本&g ...

  6. Java基础系列-二进制操作

    原创文章,转载请标注出处:<Java基础系列-二进制操作> 概述 Java源码中涉及到大量的二进制操作,非常的复杂,但非常的快速. Java二进制表示法 首先了解下二进制,二进制是相对十进 ...

  7. [Nodejs] node实现静态文件服务器

    node 静态文件处理 一般后端进行静态文件处理都是使用 Apache nginx 等静态 web 服务器,但是既然使用 node 了,就用 node 实现以下静态服务器吧. 之前弄了不少充满艺术的数 ...

  8. c# List根据某个属性进行分类,变成以属性名称作为分类的多个List

    在平时的开发中,我们从数据库中得到List列表,但是我们希望可以根据属性名称再次进行分类. 其实LINQ中已经内置相应的算法. 长话短说,直接上代码: var dataList = JsonHelpe ...

  9. [MySQL] MVCC 多版本并发控制实现的事务

    1.没有一个统一的实现标准,实现了非阻塞的读操作,写操作也只锁定必要的行2.通过保存数据在某个时间点的快照实现的3.典型的有乐观并发控制和悲观并发控制4.innodb的mvcc是每次事务都有递增的版本 ...

  10. js高德地图手机定位

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <hea ...