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, ){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 {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() => _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.();}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]:更加简练的编程体验相关推荐
- Dora.Interception,为.NET Core度身打造的AOP框架:全新的版本
Dora.Interception 1.0(可以访问GitHub地址:https://github.com/jiangjinnan/Dora)推出有一段时间了,最近花了点时间将它升级到2.0,主要有如 ...
- Dora.Interception,为.NET Core度身打造的AOP框架 [5]:轻松地实现与其他AOP框架的整合...
这里所谓的与第三方AOP框架的整合不是说改变Dora.Interception现有的编程,而是恰好相反,即在不改变现有编程模式下采用第三方AOP框架或者自行实现的拦截机制.虽然我们默认提供基于IL E ...
- Dora.Interception: 一个为.NET Core度身定制的AOP框架
多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用"注入 ...
- StarlingMVC:为Starling量身打造的MVC框架
详细了解StarlingMVC框架,请登录其官方站点: http://creativebottle.github.com/starlingMVC/ 以下中文翻译转自Starling中文站,仅供部分参考 ...
- AOP框架Dora.Interception 3.0 [1]: 编程体验
.NET Core正式发布之后,我为.NET Core度身定制的AOP框架Dora.Interception也升级到3.0.这个版本除了升级底层类库(.NET Standard 2.1)之外,我还对它 ...
- 带你学习AOP框架之Aspect.Core[1]
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的 ...
- 如何为企业量身打造一套高可用系统?
对于用户量较大的系统以及系统稳定性要求极高的企业,每一次计划外的系统停机都是严重事故.想要降低计划外系统停机的概率,有两个关键动作,一是执行计划内维护来及时更新硬件或者软件,另外一个有效的动作就是搭建 ...
- 小别墅样式_6款三层小别墅样式,各风格特色图纸为农村自建量身打造
原标题:6款三层小别墅样式,各风格特色图纸为农村自建量身打造 从最新农村建房面积规定可以看出,今后农村自建房小别墅是主流,而三层小别墅样式则是为农村家庭量身打造的别墅样式,在满足住房需求的同时,做到经 ...
- 第十八:如何定制Pytest+Allure2详细报告生成,给项目量身打造一套测试报告(重点超详细)
简介 1.俗话说"人靠衣服马靠鞍"一个项目做的在好,没有一分的漂亮的测试报告有时候也是很难在客户那边验收的,今天就带你们解决这一难题. 2.前边一篇文章是分享如何搭建pytest+ ...
最新文章
- 全球智慧医疗产业发展现状
- Oracle RMAN 清除归档日志
- Hive的HQL(2)
- what kind of supervision will i do next semester?
- catia相合约束怎么反向_人是怎么不知不觉废掉的?进来,帮你
- oracle编写备份数据库代码,oracle_oracle数据库创建备份与恢复脚本整理,1:创建用户 复制代码 代码如 - phpStudy...
- hibernate 环境搭建测试
- 初探函数式编程和面对对象式编程
- 请列举你了解的分布式锁_这几种常见的“分布式锁”写法,搞懂再也不怕面试官,安排!...
- SSL请求trustStore的两种注册方式
- html里面textfield属性,StyleableTextField的CSS属性htmlText
- Android网络通信的六种方式
- Win10下连接树莓派ZeroW(附win10虚拟网卡驱动下载)
- 月薪2w的前端工程师,必盘的实战项目
- auto semicolon insertion 自动分号补齐的坑
- 《就这样享用word》word实用技巧总结-更新中
- 如何设计一个通用的查询接口
- Macsome iTunes Converter for Mac(DRM移除和音乐转换器)3.5.0
- 2023年19个数学建模竞赛重磅来袭!!!
- 宋晓丽20190919-1 每周例行报告
热门文章
- learn python the hard way结尾_翻译学python---《Learn Python the hard Way》---第一章 绪论
- android 5.0 ios 8,iOS 8与Android 5.0大比拼:功能相同 体验不同
- ios 百度地图指定区域_iOS开发(第三方使用)——百度地图的简单使用(定位与当前位置的显示)...
- spring aop原理_Spring知识点总结!已整理成142页离线文档(源码笔记+思维导图)...
- lwip路由实现_基于LWIP协议栈对路由缓存数据结构实现改进设计
- autocad不能画图_设计院老司机谈CAD:学习AutoCAD掌握方法技巧更重要
- 转:【AI每日播报】从TensorFlow到Theano:横向对比七大深度学习框架
- Struts2后期(这框架目前正处于淘汰状态)
- 维基解密揭秘CIA五种恶意软件用法
- Jmeter之分布式测试