Autofac实现拦截器和切面编程

Autofac.Annotation框架是我用.netcore写的一个注解式DI框架,基于Autofac参考 Spring注解方式所有容器的注册和装配,切面,拦截器等都是依赖标签来完成。

开源地址:https://github.com/yuzd/Autofac.Annotation

上期说了Autofac实现有条件的DI

本期讲的是最新重构的功能,这个功能也是赋予了这个框架的无限可能,也是我觉得设计的比较好的地方, 今天来说说我是怎么设计的。本篇文章我写的时候想了很久如何脱离代码去讲的很清楚,但是发现很难,因为设计的巧妙之处还是在于代码,得细品。至少我觉得比Spring的拦截器链用的代码少而精巧,而且更容器理解(手动狗头)

d4e16c8d252cd2348db03dbde4088de8.png

切面和拦截器介绍

拦截器是什么?

可以帮助我们方便在执行目标方法的

  • 前(Before)

  • 后(After)

  • 返回值时(AfterReturn)

  • 抛错误时(AfterThrowing)

  • 环绕(Around)

简单示例:

//自己实现一个拦截器public class TestHelloBefore:AspectBefore{public override Task Before(AspectContext aspectContext){Console.WriteLine("TestHelloBefore");return Task.CompletedTask;}}[Component]public class TestHello{[TestHelloBefore]//打上拦截器public virtual void Say(){Console.WriteLine("Say");}}

先执行 TestHelloBefor的Before方法再执行你的Say方法

更多使用示例请查看 Aspect拦截器

切面是什么?

定义一个切面(根据筛选器去实现满足条件的多个类的多个方法的“拦截器”

简单示例:

[Component]public class ProductController{public virtual string GetProduct(string productId){return "GetProduct:" + productId;}public virtual string UpdateProduct(string productId){return "UpdateProduct:" + productId;}}[Component]public class UserController{public virtual string GetUser(string userId){return "GetUser:" + userId;}public virtual string DeleteUser(string userId){return "DeleteUser:" + userId;}}// *Controller 代表匹配 只要是Controller结尾的类都能匹配// Get* 代表上面匹配成功的类下 所以是Get打头的方法都能匹配[Pointcut(Class = "*Controller",Method = "Get*")]public class LoggerPointCut{[Around]public async Task Around(AspectContext context,AspectDelegate next){Console.WriteLine("PointcutTest1.Around-start");await next(context);Console.WriteLine("PointcutTest1.Around-end");}[Before]public void Before(){Console.WriteLine("PointcutTest1.Before");}[After]public void After(){Console.WriteLine("PointcutTest1.After");}[AfterReturn(Returing = "value1")]public void AfterReturn(object value1){Console.WriteLine("PointcutTest1.AfterReturn");}[AfterThrows(Throwing = "ex1")]public void Throwing(Exception ex1){Console.WriteLine("PointcutTest1.Throwing");}       }

更多示例请查看 Pointcut切面编程

如何实现的

分为3步

  • 1.搜集拦截算子(比如Before/After等这个我们叫算子)

  • 2.构造拦截器链(按照上面图的方式把算子链接起来)

  • 3.生成代理类代理目标方法去执行上面构造的拦截器链

1.搜集拦截算子

因为拦截器的使用是约定了要继承 AspectInvokeAttribute

/// <summary>///     AOP拦截器 默认包含继承关系/// </summary>[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]public class AspectInvokeAttribute : Attribute{/// <summary>///     排序 值越低,优先级越高/// </summary>public int OrderIndex { get; set; }/// <summary>///     分组名称/// </summary>public string GroupName { get; set; }}
cb1e9daf12fb8dcc705259fb509135f8.png
image

这一组注解是暴露给外部使用,来搜集哪些类的哪些方法需要增强

接下来需要去针对性去实现每一种增强器要做的事情

定义一个增强器接口IAdvice

internal interface IAdvice{/// <summary>///  拦截器方法/// </summary>/// <param name="aspectContext">执行上下文</param>/// <param name="next">下一个增强器</param>/// <returns></returns>Task OnInvocation(AspectContext aspectContext, AspectDelegate next);}
18e6f5218f31a85e20651a65e75b8455.png
image

Before增强器

/// <summary>/// 前置增强器/// </summary>internal class AspectBeforeInterceptor : IAdvice{private readonly AspectBefore _beforeAttribute;public AspectBeforeInterceptor(AspectBefore beforeAttribute){_beforeAttribute = beforeAttribute;}public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next){//先执行Before逻辑await this._beforeAttribute.Before(aspectContext);//在走下一个增强器await next.Invoke(aspectContext);}}

After增强器

/// <summary>/// 后置增强器/// </summary>internal class AspectAfterInterceptor : IAdvice{private readonly AspectAfter _afterAttribute;private readonly bool _isAfterAround;public AspectAfterInterceptor(AspectAfter afterAttribute, bool isAfterAround = false){_afterAttribute = afterAttribute;_isAfterAround = isAfterAround;}public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next){try{if (!_isAfterAround) await next.Invoke(aspectContext);}finally{//不管成功还是失败都会执行的 await this._afterAttribute.After(aspectContext, aspectContext.Exception ?? aspectContext.ReturnValue);}}}

环绕增强器

/// <summary>/// 环绕返回拦截处理器/// </summary>internal class AspectAroundInterceptor : IAdvice{private readonly AspectArround _aroundAttribute;private readonly AspectAfterInterceptor _aspectAfter;private readonly AspectAfterThrowsInterceptor _aspectThrows;public AspectAroundInterceptor(AspectArround aroundAttribute, AspectAfter aspectAfter, AspectAfterThrows chainAspectAfterThrows){_aroundAttribute = aroundAttribute;if (aspectAfter != null){_aspectAfter = new AspectAfterInterceptor(aspectAfter, true);}if (chainAspectAfterThrows != null){_aspectThrows = new AspectAfterThrowsInterceptor(chainAspectAfterThrows, true);}}public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next){Exception exception = null;try{if (_aroundAttribute != null){await _aroundAttribute.OnInvocation(aspectContext, next);return;}}catch (Exception ex){exception = ex;}finally{if (exception == null && _aspectAfter != null) await _aspectAfter.OnInvocation(aspectContext, next);}try{if (exception != null && _aspectAfter != null){await _aspectAfter.OnInvocation(aspectContext, next);}if (exception != null && _aspectThrows != null){await _aspectThrows.OnInvocation(aspectContext, next);}}finally{if (exception != null) throw exception;}}}

返回值增强器

/// <summary>/// 后置返值增强器/// </summary>internal class AspectAfterReturnInterceptor : IAdvice{private readonly AspectAfterReturn _afterAttribute;public AspectAfterReturnInterceptor(AspectAfterReturn afterAttribute){_afterAttribute = afterAttribute;}public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next){await next.Invoke(aspectContext);//执行异常了不执行after 去执行Throwif (aspectContext.Exception != null){return;}if (_afterAttribute != null){await this._afterAttribute.AfterReturn(aspectContext, aspectContext.ReturnValue);}}}

异常返回增强器

/// <summary>/// 异常返回增强器/// </summary>internal class AspectAfterThrowsInterceptor : IAdvice{private readonly AspectAfterThrows _aspectThrowing;private readonly bool _isFromAround;public AspectAfterThrowsInterceptor(AspectAfterThrows throwAttribute, bool isFromAround = false){_aspectThrowing = throwAttribute;_isFromAround = isFromAround;}public async Task OnInvocation(AspectContext aspectContext, AspectDelegate next){try{if (!_isFromAround) await next.Invoke(aspectContext);}finally{//只有目标方法出现异常才会走 增强的方法出异常不要走if (aspectContext.Exception != null){Exception ex = aspectContext.Exception;if (aspectContext.Exception is TargetInvocationException targetInvocationException){ex = targetInvocationException.InnerException;}if (ex == null){ex = aspectContext.Exception;}var currentExType = ex.GetType();if (_aspectThrowing.ExceptionType == null || _aspectThrowing.ExceptionType == currentExType){await _aspectThrowing.AfterThrows(aspectContext, aspectContext.Exception);}}}}}

2. 组装增强器们成为一个调用链

9ecac031c6228e01ca9fdc92672ea5e1.png
image

每一个node的有三个信息,如下

/// <summary>/// 拦截node组装/// </summary>internal class AspectMiddlewareComponentNode{/// <summary>/// 下一个/// </summary>public AspectDelegate Next;/// <summary>/// 执行器/// </summary>public AspectDelegate Process;/// <summary>/// 组件/// </summary>public Func<AspectDelegate, AspectDelegate> Component;}

采用LinkedList来构建我们的拉链式调用, 我们把上面的每个增强器作为一个个middeware,添加进来。

internal class AspectMiddlewareBuilder{private readonly LinkedList<AspectMiddlewareComponentNode> Components = new LinkedList<AspectMiddlewareComponentNode>();/// <summary>/// 新增拦截器链/// </summary>/// <param name="component"></param>public void Use(Func<AspectDelegate, AspectDelegate> component){var node = new AspectMiddlewareComponentNode{Component = component};Components.AddLast(node);}/// <summary>/// 构建拦截器链/// </summary>/// <returns></returns>public AspectDelegate Build(){var node = Components.Last;while (node != null){node.Value.Next = GetNextFunc(node);node.Value.Process = node.Value.Component(node.Value.Next);node = node.Previous;}return Components.First.Value.Process;}/// <summary>/// 获取下一个/// </summary>/// <param name="node"></param>/// <returns></returns>private AspectDelegate GetNextFunc(LinkedListNode<AspectMiddlewareComponentNode> node){return node.Next == null ? ctx => Task.CompletedTask : node.Next.Value.Process;}}

然后build方法会构建成一个一层嵌套一层的pipeline管道(一个委托)

765bdaa9485d634e7e2e6b5cdd9b0992.png
image

更多关于这种设计模式更多信息请参考我另外一篇文章:中间件(middlewware)模式

构建的顺序: After, Around,Before,最后才是实际的方法

这里尤其要注意需要将AfterReturn 和 AfterThrowing在Around增强器里面调用,这里要细品!

按照我们的需求构建的完整执行示意图如下:

单个拦截器或者切面
45b716210c2b0c677b692a010b50df67.png
image
多个拦截器或者切面
2cee762f6b40530582ceb9c61a23b931.png
image

3.生成代理类代理目标方法去执行上面构造的拦截器链

这一步就简单了,如果检测到目标有打拦截器注解,则会给这个类动态创建一个proxy类,

生成代理类用的是castle.core的dynamic组件

默认的是Class+virtual的方式对目标方法进行拦截

88c42bfcbfee9b6cc01294fa5cd2eeeb.png
image

注意:考虑到性能,在项目启动的时候把构建好进行缓存,然后再拦截器里面使用

https://github.com/yuzd/Autofac.Annotation/wiki


我是正东,学的越多不知道也越多。热爱可低漫长岁月,这一刻我很爽!

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

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

相关文章

[探索 .NET 6]01 揭开 ConfigurationManager 的面纱

在这个系列中&#xff0c;我将探索一下 .NET 6 中的一些新特性。已经有很多关于 .NET 6 的内容&#xff0c;包括很多来自 .NET 和 ASP.NET 团队本身的文章。在这个系列中&#xff0c;我将探索一下这些特性背后的一些代码。在这第一篇文章中&#xff0c;来研究一下 Configuratio…

mysql锁表_MySQL中Alter table 你不知道的性能问题

前言&#xff1a;MySQL 的大表运维总是令人头疼的一件事&#xff0c;特别是大表表结构的修改尤为困难。首先&#xff0c;alter table 的process不可被kill &#xff0c; 一旦执行就不可回退。其次&#xff0c;大多数的alter table操作都会涉及 lock --- copy to new table --- …

打印文件前,千万记得把弹窗叉掉!!!

1 父母能有多迷信&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 打印前千万记得把弹窗关掉&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 挺好的&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 饭桌上&#xff0c;领导叫你去…

高效的动态URL限流实现

访问限流对于一个网关来说是个比较重要的功能&#xff0c;它可以根据不同服务的处理能力来控制相关的访问量&#xff0c;从而保障服务更可靠地运行。但是URL控制的路径比较多还加上可动态添加删除&#xff0c;大量的访问匹配会很容易引起性能上的问题&#xff0c;接下来讲解一下…

cocos2d-x学习 之一

最近准备学习cocos2d-x的开发&#xff0c;首先要搭建一下开发环境。今天就先搭建一下开发环境。本人系统为Mint-15 64位的linux&#xff0c;以下的开发环境只用于linux。首先到cocos2d-x的官网上下载安装包&#xff0c;由于cocos2d-x是开源的&#xff0c;所以我们可以查看源码&…

在 App 扩展和主 App 间共享数据

tags: iOS 8,Swift,App Groups 随着 iOS 8 的发布&#xff0c;苹果为广大开发者很多新的 API&#xff0c;其中最突出显著的就非 App Extension 莫属了。这为开发者们又带来了很多机会。 而我们在开发 App Extension 的时候&#xff0c;基本大多数人都会遇到这样一个问题。就是由…

[探索 .NET 6]02 比较 WebApplicationBuilder 和 Host

这是『探索 .NET 6』系列的第二篇文章&#xff1a;01 揭开 ConfigurationManager 的面纱02 比较 WebApplicationBuilder 和 Host在 .NET 中&#xff0c;有一种新的“默认”方法用来构建应用程序&#xff0c;即使用 WebApplication.CreateBuilder()。在这篇文章中&#xff0c;我…

都怪爱因斯坦没说清楚!竟有人相信一个粉笔头就能让全人类多喝100年的热水?...

全世界只有3.14 % 的人关注了爆炸吧知识一个粉笔头一共能释放多少能量爱因斯坦大家肯定都熟悉&#xff0c;相信也有很多朋友听说过质能方程。根据质能方程的公式&#xff0c;我们发现&#xff1a;似乎能量和质量是可以相互转化的。尤其是一些没有系统学习过相对论&#xff0c;又…

从微信云托管容器镜像的选择-alpine 说起

微信云托管 使用目前主流的容器平台Docker以及容器编排技术Kubernetes&#xff08;简称K8S&#xff09;&#xff0c;来管理你的项目。使用微信云托管需要掌握对Docker的使用&#xff0c;但你无需掌握K8S的使用方法。微信云托管将K8S的运维配置完全接手&#xff0c;你不需要关心…

H5移动开发AUI框架入门---博客园老牛大讲堂

大家都知道H5可以开发移动端的页面&#xff0c;网上提供的移动端的开发都有很多。因为我学习了AUI框架&#xff0c;所以我这里介绍一下移动端AUI框架。--博客园老牛大讲堂 一、AUI框架是什么&#xff1f;---博客园老牛大讲堂 AUI框架就是利用原生的js和css封装成的一些界面。当…

.NET6使用DOCFX根据注释自动生成开发文档

本文内容来自我写的开源电子书《WoW C#》&#xff0c;现在正在编写中&#xff0c;可以去WOW-Csharp/学习路径总结.md at master sogeisetsu/WOW-Csharp (github.com)来查看编写进度。预计2021年年底会完成编写&#xff0c;2022年2月之前会完成所有的校对和转制电子书工作&…

量子力学到底神奇在哪里?看完这个,我的认知彻底坍塌了

▲ 点击查看很多朋友应该都看过Facebook创始人扎克伯格给他的女儿讲量子力学的那张照片。扎克伯格在清华大学经济管理学院做演讲时&#xff0c;曾谈到&#xff1a;学习量子力学改变了他的思维方式。到底什么是量子力学&#xff1f;我们生活面对的物质尺度大约是厘米级到千米级之…

linux 路由表设置 之 route 指令详解

使用下面的 route 命令可以查看 Linux 内核路由表。 [cpp] view plaincopy# route Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.0.0 * 255.255.255.0 U 0 0 0 eth0 169.254.0.0 * …

黄老师离开呆了十年的上海

关注我的老朋友都知道&#xff0c;我和一线码农&#xff08;黄新成&#xff09;以前是同事&#xff0c;我以前也写过文章说过他的事迹。我们曾经一起共事过一家电商服务公司&#xff0c;每天和千万量级的数据打交道。.NET 圈的朋友想必很多人都看过一线码农写的技术文章&#x…

Shell配置_配置IP

1、setup 打开图形化页面a) 选择网络配置b) 选择设置配置c) 选择第一个网卡2、启动网卡&#xff08;第一个网卡&#xff09;vim /etc/sysconfig/network-scripts/ifcfg-eth0将ONBOOT"no"改为ONBOOT"yes"3、重启网络服务service network restart来自为知笔记…