为了支持AOP的编程模式,我为.NET Core写了一个轻量级的Interception框架[开源]

ASP.NET Core具有一个以ServiceCollection和ServiceProvider为核心的依赖注入框架,虽然这只是一个很轻量级的框架,但是在大部分情况下能够满足我们的需要。不过我觉得它最缺乏的是针对AOP的支持,虽然这个依赖注入框架提供了扩展点使我们可以很容易地实现与第三方框架的集成,但是我又不想“节外生枝”,为此我们趁这个周末写了一个简单的Interception框架来解决这个问题。通过这个命名为Dora.Interception的框架,我们可以采用一种非常简单、直接而优雅地(呵呵)在这个原生的DI框架上实现针对AOP的编程。目前这只是一个Beta(Beta1)版本,我将它放到了github上(https://github.com/jiangjinnan/Dora)。我写这篇文章不是为了说明这个Dora.Interception的设计和实现原理,而是为了介绍如何利用它在一个ASP.NET Core与原生的DI框架结合实现AOP的编程模式。两个实例可以从这里下载。

一、基本原理

和大部分针AOP/Interception的实现一样,我们同样采用“代理”的方式实现对方法调用的拦截和注入。如下图所示,我们将需要以AOP方法注入的操作定义成一个个的Interceptor,并以某种方式(我采用的是最为直接的标注Attribute的形式)应用到某个类型或者方法上。在运行的时候我们为目标对象创建一个代理,我们针对代理对象的调用将会自动传递到目标对象。不过在目标对象最终被调用的时候,注册的Interceptor会按照顺序被先后执行。

二、安装NuGet包

这个框架目前涉及到如下两个框架,基础的模型实现在Dora.Interception这个包中,Dora.Interception.Castle则利用Castle.DynamicProxy针对代理的创建提供了一个默认实现。

  • Dora.Interception

  • Dora.Interception.Castle

这两个NuGet包已经上传到nuget.org,所以我们可以直接使用它们。假设我们创建了一个空的ASP.NET Core控制台应用,我们可以通过执行如下的命名

三、定义Interceptor

假设我们创建这样一个Interceptor,它能够捕获后续执行过程中抛出的异常,并将异常消息写入日志,我们将这个Interceptor命名为ErrorLogger。如下所示的就是这个ErrorLogger的完整定义。

   1: public class ErrorLogger
   2: {
   3:     private InterceptDelegate _next;
   4:     private ILogger _logger;
   5:     public ErrorLogger(InterceptDelegate next, ILoggerFactory loggerFactory, string category)
   6:     {
   7:         _next     = next;
   8:         _logger   = loggerFactory.CreateLogger(category);
   9:     }
  10:  
  11:     public async Task InvokeAsync(InvocationContext context)
  12:     {
  13:         try
  14:         {
  15:             await _next(context);
  16:         }
  17:         catch (Exception ex)
  18:         {
  19:             _logger.LogError(ex.Message);
  20:             throw;
  21:         }
  22:     }
  23: }

考虑到依赖注入的使用,我们并没有为具体的Interceptor类型定义一个接口,用户仅仅需要按照如下的约定来定义这个Interceptor类型就可以了。对ASP.NET Core的管道设计比较熟悉的人应该可以看出这与中间件的设计是一致的。

  • Interceptor具有一个这样一个公共构造函数:它的第一个参数是一个InterceptDelegate 类型的委托,我们通过它调用后续的Interceptor或者目标对象。我们并不对后续的参数做任何约束,它们可以采用DI的方式进行注入(比如上面的loggerFactory参数)。如果不能以DI的形式提供的参数(比如参数category),在后面注册的时候需要显式指定。

  • 拦截注入的功能虚线实现在一个名为InvokeAsync的方法中,该方法的需要返回一个Task对象,并且要求方法中包含一个类型为InvocationContext 的对象,该对象表示执行代理方法的执行上下文。如下面的代码片段所示,我们不仅仅可以得到与当前方法调用相关的上下文信息,还可以直接利用它设置参数的值和最终返回的值。InvokeAsync方法需要自行决定是否继续调用后续的Interceptor和目标对象,这可以直接通过在构造函数中指定的这个InterceptDelegate 来完成。

   1: namespace Dora.Interception
   2: {
   3:     public abstract class InvocationContext
   4:     {
   5:         protected InvocationContext();
   6:  
   7:         public abstract object[] Arguments { get; }
   8:         public abstract Type[] GenericArguments { get; }
   9:         public abstract object InvocationTarget { get; }
  10:         public abstract MethodInfo Method { get; }
  11:         public abstract MethodInfo MethodInvocationTarget { get; }
  12:         public abstract object Proxy { get; }
  13:         public abstract object ReturnValue { get; set; }
  14:         public abstract Type TargetType { get; }
  15:  
  16:         public abstract object GetArgumentValue(int index);
  17:         public abstract void SetArgumentValue(int index, object value);
  18:     }
  19: }

由于构造函数和InvokeAsync方法都支持依赖注入,所以ErrorLogger也可以定义成如下的形式(ILoggerFactory 在InvokeAsync方法中注入)。

   1: public class ErrorLogger
   2: {
   3:     private InterceptDelegate _next;
   4:     private string  _category;
   5:     public ErrorLogger(InterceptDelegate next,  string category)
   6:     {
   7:         _next = next;
   8:         _category = category;
   9:     }
  10:  
  11:     public async Task InvokeAsync(InvocationContext context, ILoggerFactory loggerFactory)
  12:     {
  13:         try
  14:         {
  15:             await _next(context);
  16:         }
  17:         catch (Exception ex)
  18:         {
  19:             loggerFactory.CreateLogger(_category).LogError(ex.Message);
  20:             throw;
  21:         }
  22:     }
  23: }

四、定义InterceptorAttribute

由于我们采用标注Attribute的方式,我们为这样的Attribute定义了一个名为InterceptorAttribute的基类。针对ErrorLogger的ErrorLoggerAttribute定义如下,它的核心在与需要实现抽象方法Use并利用作为参数的IInterceptorChainBuilder 注册对应的ErrorLogger。IInterceptorChainBuilder 中定义了一个泛型的方法使我们很容易地实现针对某个Interceptor类型的注册。该方法的第一个参数是整数,它决定注册的Interceptor在整个Interceptor有序列表中的位置。InterceptorAttribute中定义了对应的Order属性。如果注册Interceptor类型的构造还是具有不能通过依赖注入的参数,我们需要在调用Use方法的时候显式指定(比如category)。

   1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method, AllowMultiple = false)]
   2: public class ErrorLoggerAttribute : InterceptorAttribute
   3: {
   4:     private string _category;
   5:  
   6:     public ErrorLoggerAttribute(string category)
   7:     {
   8:         _category = category;
   9:     }
  10:     public override void Use(IInterceptorChainBuilder builder)
  11:     {
  12:         builder.Use<ErrorLogger>(this.Order, _category);
  13:     }
  14: }

InterceptorAttribute可以应用在类和方法上(我不赞成将它应用到接口上),在默认情况下它的AllowMultiple 属性为False。如果我们希望Interceptor链中可以包含多个相同类型的Interceptor,我们可以将AllowMultiple 属性设置为True。值得一提的是,在AllowMultiple 属性为False的情况下,如果类型和方法上都应用了同一个InterceptorAttribute,那么只会选择应用在方法上的那一个。在如下的代码中,我们将ErrorLoggerAttribute应用到总是会抛出异常的Invoke方法中,并且将日志类型设置为“App”。

   1: public interface IFoobarService
   2: {
   3:     void Invoke();
   4: }
   5:  
   6: public class FoobarService : IFoobarService
   7: {
   8:     [ErrorLogger("App")]
   9:     public void Invoke()
  10:     {
  11:         throw new InvalidOperationException("Manually thrown exception!");
  12:     }
  13: }

五、以DI的方式注入代理

我们依然会以DI的方式来使用上面定义的服务IFoobarService,但是毫无疑问,注入的对象必须是目标对象(FoobarService)的代理,我们注册的Interceptor才能生效,为了达到这个目的,我们需要使用如下这个IInterceptable<T>接口,它的Proxy属性为我们返回需要的代理对象。

   1: namespace Dora.Interception
   2: {
   3:     public interface IInterceptable<T> where T : class
   4:     {
   5:         T Proxy { get; }
   6:     }
   7: }

比如我们选在在MVC应用中将IFoobarService注入到Controller中,我们可以采用如下的定义方式。

   1: public class HomeController
   2: {
   3:     private IFoobarService _service;
   4:     public HomeController(IInterceptable<IFoobarService> interceptable)
   5:     {
   6:         _service = interceptable.Proxy;
   7:     }
   8:     [HttpGet("/")]
   9:     public string Index()
  10:     {
  11:         _service.Invoke();
  12:         return "Hello World";
  13:     }
  14: }

接下来我们来完成这个应用余下的部分。如下面的代码片段所示,我们在作为启动类Startup的ConfigureServicves方法中调用IServiceCollection的扩展方法AddInterception注册于Interception相关的服务。为了确定ErrorLogger是否将异常信息写入日志,我们在Main方法中添加了针对ConsoleLoggerProvider的注册,并选择只写入类型为“App”的日志。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         new WebHostBuilder()
   6:             .ConfigureLogging(factory=>factory.AddConsole((category, level)=>category == "App"))
   7:             .UseKestrel()
   8:             .UseStartup<Startup>()
   9:             .Build()
  10:             .Run();
  11:     }
  12: }
  13:  
  14: public class Startup
  15: {
  16:     public void ConfigureServices(IServiceCollection services)
  17:     {
  18:         services
  19:             .AddInterception()
  20:             .AddScoped<IFoobarService, FoobarService>()
  21:             .AddMvc();
  22:     }
  23:  
  24:     public void Configure(IApplicationBuilder app)
  25:     {
  26:         app.UseDeveloperExceptionPage()
  27:             .UseMvc();
  28:     }
  29: }

运行该应用后,如果我们利用浏览器访问该应用,由于我们注册了DeveloperExceptionPageMiddleware中间件,所以会出入如下图所示的错误页面。而服务端的控制台会显示记录下的错误日志。

六、如果你不喜欢IInterceptable<T>接口

Interception自身的特质决定我们只有注入目标对象的代理才能让注册的Interceptor被执行,这个问题我们是利用IInterceptable<T>接口来实现的,可能有人觉得这种方法不是很爽的话,我们还有更好的解决方案。我们先将HomeController写成正常的形式。

   1: public class HomeController
   2: {
   3:     private IFoobarService _service;
   4:     public HomeController(IFoobarService service)
   5:     {
   6:         _service = service;
   7:     }
   8:     [HttpGet("/")]
   9:     public string Index()
  10:     {
  11:         _service.Invoke();
  12:         return "Hello World";
  13:     }
  14: }

接下来我们需要在Startup的ConfigureServices方法调用ServiceCollection的ToInterceptable方法即可。

   1: public class Startup
   2: {
   3:     public void ConfigureServices(IServiceCollection services)
   4:     {
   5:         services
   6:             .AddInterception()
   7:             .AddScoped<IFoobarService, FoobarService>()
   8:             .AddMvc();
   9:         services.ToInterceptable();
  10:     }
  11:  
  12:     public void Configure(IApplicationBuilder app)
  13:     {
  14:         app.UseDeveloperExceptionPage()
  15:             .UseMvc();
  16:     }
  17: }

目前来说,如果采用这种方法,我们需要让注入的服务实现一个空的IInterceptable接口,因为我会利用它来确定某个对象是否需要封装成代理,将来我会将这个限制移除。

   1: public class FoobarService : IFoobarService, IInterceptable
   2: {
   3:     [ErrorLogger("App")]
   4:     public void Invoke()
   5:     {
   6:         throw new InvalidOperationException("Manually thrown exception!");
   7:     }
   8: }

原文地址:http://www.cnblogs.com/artech/p/dora-initerception.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327207.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

装箱VS拆箱

我们一般将“基本数据类型转换成包装类”的过程叫做装箱&#xff0c;将“包装类转换成基本数据类型”的过程叫做拆箱。 装箱可以分为手动装箱和自动装箱&#xff1a; 拆箱也可以分为手动拆箱和自动拆箱&#xff1a;

事务模型与分布式事务总结思考

转载自 事务模型与分布式事务总结思考 1. 介绍 之前了解过一些分布式事务处理的思想&#xff0c;包括MVCC、TCC等。但是对具体实现的规范和约束还不够理解清晰。本文从事务模型分类来讨论常见的事务模型。事务模型的含义&#xff0c;应该指的是我们如何去使用可控制事务。 首…

Java连接PostgreSQL数据库,增删改查

https://blog.csdn.net/u013456370/article/details/79668420 通过eclipse工具&#xff0c;新建Maven项目&#xff1a; 添加&#xff1a;postgresql的jar包&#xff08;我使用的是&#xff1a;版本&#xff1a;42.2.2&#xff09; 修改pom.xml文件&#xff1a; <!-- https…

java打印九九乘法表——CSDN博客

/*** * Title: test_jiujiu* Description: 该方法的主要作用&#xff1a;九九乘法表* param 设定文件 * return 返回类型&#xff1a;void * throws*/Testpublic void test_jiujiu(){//打印九九乘法表for (int i 1; i < 10; i) {for (int j 1; j < i; j) {System…

我的这10年——从机械绘图 到 炼油 到 微软MVP 的华丽转身

年底了&#xff0c;各种总结计划满天飞&#xff0c;有空的时候我也一直在思考这么多年&#xff0c;是怎么过来的。也曾经很迷茫&#xff0c;希望经验和经历能给大家一点带来一点正能量的东西。10年很长&#xff0c;10年前说实话我没有思考过现在的样子&#xff0c;但10年前的日…

MySQL中的共享锁与排他锁

转载自 MySQL中的共享锁与排他锁 在MySQL中的行级锁,表级锁,页级锁中介绍过&#xff0c;行级锁是Mysql中锁定粒度最细的一种锁&#xff0c;行级锁能大大减少数据库操作的冲突。行级锁分为共享锁和排他锁两种&#xff0c;本文将详细介绍共享锁及排他锁的概念、使用方式及注意事项…

基本类型和字符串互相转换

将基本数据类型转换成字符串 将字符串转换成基本数据类型

java操作字符串——CSDN博客

/*** * Title: test_class_arrat* Description: 该方法的主要作用&#xff1a;* param 设定文件 * return 返回类型&#xff1a;void * throws*/Testpublic void test_is_equals(){//String str1new String("我爱祖国。");//String str2new String("我爱祖…

C#如何使用ES

Elasticsearch简介 Elasticsearch &#xff08;ES&#xff09;是一个基于 Lucene 的开源搜索引擎&#xff0c;它不但稳定、可靠、快速&#xff0c;而且也具有良好的水平扩展能力&#xff0c;是专门为分布式环境设计的。 Elasticsearch是什么 Elasticsearch是一个基于Apache Luc…

MySQL中的读锁和写锁

转载自 MySQL中的读锁和写锁 在数据库的锁机制中介绍过&#xff0c;数据的锁主要用来保证数据的一致性的&#xff0c;数据库的锁从锁定的粒度上可以分为表级锁、行级锁和页级锁。在我的博客中重点介绍过MySQL数据库的行级锁。这篇文章主要来介绍一下MySQL数据库中的表级锁。 本…

springboot中配置mybatis连接postgresql

https://blog.csdn.net/y_qc_lookup/article/details/80178545 springboot中配置mybatis连接postgresql 置顶 Dylans 2018-05-03 15:49:46 41415 收藏 8 分类专栏&#xff1a; java 文章标签&#xff1a; springboot mybatis postgresql xml 版权 最近在使用springboot用于…

java向数组中插入元素

/*** * Title: test_insert_array* Description: 该方法的主要作用&#xff1a;像数组中插入元素* param 设定文件 * return 返回类型&#xff1a;void * throws*/Testpublic void test_insert_array(){Scanner scanner new Scanner(System.in);int [] list new …

CoreCLR源码探索(二) new是什么

前一篇我们看到了CoreCLR中对Object的定义&#xff0c;这一篇我们将会看CoreCLR中对new的定义和处理new对于.Net程序员们来说同样是耳熟能详的关键词&#xff0c;我们每天都会用到new&#xff0c;然而new究竟是什么&#xff1f; 因为篇幅限制和避免难度跳的太高&#xff0c;这一…

Mysql中的行级锁、表级锁、页级锁

转载自 Mysql中的行级锁、表级锁、页级锁 在计算机科学中&#xff0c;锁是在执行多线程时用于强行限制资源访问的同步机制&#xff0c;即用于在并发控制中保证对互斥要求的满足。 在数据库的锁机制中介绍过&#xff0c;在DBMS中&#xff0c;可以按照锁的粒度把数据库锁分为行级…

Math中的常用方法

package educoder; public class MathTest{public static void main(String args[]){ /** *Math.sqrt()//计算平方根*Math.cbrt()//计算立方根*Math.pow(a, b)//计算a的b次方*Math.max( , );//计算最大值*Math.min( , );//计算最小值*/System.out.println(Math.sqrt(16)); //4…

Springmvc中提交from之后不跳转不进控制器

今天在自学springmvc之后写了一个简单的案例&#xff0c;可是不管怎么改都不进入控制器Controller&#xff0c;找了好久之后原来是粗心有个地方写错了&#xff0c;详情请往下看&#xff1a; 在springmvx-servlet.xml里面&#xff1a; <!-- 配置HandlerMapping映射&#xff…

asp.net core 认证及简单集群

众所周知&#xff0c;在Asp.net WebAPI中&#xff0c;认证是通过AuthenticationFilter过滤器实现的&#xff0c;我们通常的做法是自定义AuthenticationFilter&#xff0c;实现认证逻辑&#xff0c;认证通过&#xff0c;继续管道处理&#xff0c;认证失败&#xff0c;直接返回认…

Java开发2018年值得学习的10大技术

转载自 Java开发2018年值得学习的10大技术 作为一个开发人员&#xff0c;我们最大的挑战就是保持自己了解新的技术。技术变化很快,你大概每两年就会看到一个新版本的编程语言和框架。 就拿2017年来说&#xff0c;AR、VR、区块链、人工智能等等已经扑面而来了。除了这些离我们…