Rougamo、Fody 实现静态Aop

news/2024/7/5 20:38:25/文章来源:https://www.cnblogs.com/KarlAlbright/p/18277740

最近在看项目,看到别人使用Rougamo框架,好奇花了点时间仔细研究了,在这里记录一下。

0. 静态编织 Aop

首先,我们先了解什么是Aop? Aop 是指面向切面编程 (Aspect Oriented Programming),而所谓的切面,可以认为是具体拦截的某个业务点。

我们常用的aop框架是 AspectCore,他是属于动态代理,也就是发生在运行时期间对代码进行“修改”。

Rougamo、Fody 是属于静态编织,是指在编译阶段将代码修改或额外的功能直接嵌入到程序集中,这个过程发生在源代码被编译成可执行文件或库之前。这意味着,一旦编译完成,插入的代码就已经是程序集的一部分,无需在运行时再进行额外的操作。

 

1. Rougamo 肉夹馍

Rougamo 是一个开源项目,github: https://github.com/inversionhourglass/Rougamo,他是通过Fody ->  Mono.Cecil 的方式实现静态编织 实现Aop功能。

创建控制台程序,Nuget安装 Rougamo.Fody

[AttributeUsage(AttributeTargets.Method)]
public class LoggingAttribute : MoAttribute
{public override void OnEntry(MethodContext context){Console.WriteLine("执行方法 {0}() 开始,参数:{1}.", context.Method.Name, JsonConvert.SerializeObject(context.Arguments));}public override void OnException(MethodContext context){Console.WriteLine("执行方法 {0}() 异常,{1}.", context.Method.Name, context.Exception.Message);}public override void OnExit(MethodContext context){Console.WriteLine("执行方法 {0}() 结束.", context.Method.Name);}public override void OnSuccess(MethodContext context){Console.WriteLine("执行方法 {0}() 成功.", context.Method.Name);}
}
internal class Program
{static void Main(string[] args){Add(1, 2);AddAsync(1, 2);Divide(1, 2);}[Logging]static int Add(int a, int b) => a + b;[Logging]static Task<int> AddAsync(int a, int b) => Task.FromResult(a + b);[Logging]static decimal Divide(decimal a, decimal b) => a / b;
}

运行后会指定创建FodyWeavers.xsd 和 FodyWeavers.xml

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --><xs:element name="Weavers"><xs:complexType><xs:all><xs:element name="Rougamo" minOccurs="0" maxOccurs="1" type="xs:anyType" /></xs:all><xs:attribute name="VerifyAssembly" type="xs:boolean"><xs:annotation><xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation></xs:annotation></xs:attribute><xs:attribute name="VerifyIgnoreCodes" type="xs:string"><xs:annotation><xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation></xs:annotation></xs:attribute><xs:attribute name="GenerateXsd" type="xs:boolean"><xs:annotation><xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation></xs:annotation></xs:attribute></xs:complexType></xs:element>
</xs:schema>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"><Rougamo />
</Weavers>

下面是运行结果

 这时候我们可以看到 增加了LoggingAttribute 特性的方法在运行前、运行成功、运行结束 执行了 OnEntry(MethodContext context) 、OnSuccess(MethodContext context)、OnExit(MethodContext context) 方法,这时我们打开ILSpy工具,看看实际运行的代码

internal class Program
{private static void Main(string[] args){Add(1, 2);AddAsync(1, 2);Divide(1m, 2m);}[DebuggerStepThrough]private static int Add(int a, int b){LoggingAttribute loggingAttribute = new LoggingAttribute();IMo[] mos = new IMo[1] { loggingAttribute };MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });loggingAttribute.OnEntry(methodContext);int result = default(int);if (methodContext.ReturnValueReplaced){result = (int)methodContext.ReturnValue;loggingAttribute.OnExit(methodContext);return result;}if (methodContext.RewriteArguments){a = (int)methodContext.Arguments[0];b = (int)methodContext.Arguments[1];}bool flag = default(bool);do{try{while (true){try{flag = false;result = $Rougamo_Add(a, b);}catch (Exception exception){methodContext.Exception = exception;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnException(methodContext);if (methodContext.RetryCount > 0){continue;}if (methodContext.ExceptionHandled){result = (int)methodContext.ReturnValue;break;}throw;}break;}}finally{if (methodContext.HasException || methodContext.ExceptionHandled){goto IL_0160;}methodContext.ReturnValue = result;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnSuccess(methodContext);if (methodContext.RetryCount <= 0){if (methodContext.ReturnValueReplaced){result = (int)methodContext.ReturnValue;}goto IL_0160;}flag = true;goto end_IL_00fc;IL_0160:loggingAttribute.OnExit(methodContext);end_IL_00fc:;}}while (flag);return result;}[DebuggerStepThrough]private static Task<int> AddAsync(int a, int b){LoggingAttribute loggingAttribute = new LoggingAttribute();IMo[] mos = new IMo[1] { loggingAttribute };MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });loggingAttribute.OnEntry(methodContext);Task<int> result = default(Task<int>);if (methodContext.ReturnValueReplaced){result = (Task<int>)methodContext.ReturnValue;loggingAttribute.OnExit(methodContext);return result;}if (methodContext.RewriteArguments){a = (int)methodContext.Arguments[0];b = (int)methodContext.Arguments[1];}bool flag = default(bool);do{try{while (true){try{flag = false;result = $Rougamo_AddAsync(a, b);}catch (Exception exception){methodContext.Exception = exception;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnException(methodContext);if (methodContext.RetryCount > 0){continue;}if (methodContext.ExceptionHandled){result = (Task<int>)methodContext.ReturnValue;break;}throw;}break;}}finally{if (methodContext.HasException || methodContext.ExceptionHandled){goto IL_015b;}methodContext.ReturnValue = result;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnSuccess(methodContext);if (methodContext.RetryCount <= 0){if (methodContext.ReturnValueReplaced){result = (Task<int>)methodContext.ReturnValue;}goto IL_015b;}flag = true;goto end_IL_00fc;IL_015b:loggingAttribute.OnExit(methodContext);end_IL_00fc:;}}while (flag);return result;}[DebuggerStepThrough]private static decimal Divide(decimal a, decimal b){LoggingAttribute loggingAttribute = new LoggingAttribute();IMo[] mos = new IMo[1] { loggingAttribute };MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });loggingAttribute.OnEntry(methodContext);decimal result = default(decimal);if (methodContext.ReturnValueReplaced){result = (decimal)methodContext.ReturnValue;loggingAttribute.OnExit(methodContext);return result;}if (methodContext.RewriteArguments){a = (decimal)methodContext.Arguments[0];b = (decimal)methodContext.Arguments[1];}bool flag = default(bool);do{try{while (true){try{flag = false;result = $Rougamo_Divide(a, b);}catch (Exception exception){methodContext.Exception = exception;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnException(methodContext);if (methodContext.RetryCount > 0){continue;}if (methodContext.ExceptionHandled){result = (decimal)methodContext.ReturnValue;break;}throw;}break;}}finally{if (methodContext.HasException || methodContext.ExceptionHandled){goto IL_0160;}methodContext.ReturnValue = result;methodContext.Arguments[0] = a;methodContext.Arguments[1] = b;loggingAttribute.OnSuccess(methodContext);if (methodContext.RetryCount <= 0){if (methodContext.ReturnValueReplaced){result = (decimal)methodContext.ReturnValue;}goto IL_0160;}flag = true;goto end_IL_00fc;IL_0160:loggingAttribute.OnExit(methodContext);end_IL_00fc:;}}while (flag);return result;}[Logging]private static int $Rougamo_Add(int a, int b){return a + b;}[Logging]private static Task<int> $Rougamo_AddAsync(int a, int b){return Task.FromResult(a + b);}[Logging]private static decimal $Rougamo_Divide(decimal a, decimal b){return a / b;}
}

从实际运行的代码我们可以看到,原先Add(int a, int b)方法中的执行内容被移动到 $Rougamo_Add方法中,而Add(int a, int b)方法先是new LoggingAttribute() 和 new Rougamo.Context.MethodContext() -> 执行了 loggingAttribute.OnEntry(methodContext); -> 在do{}while(bool) 执行了$Rougamo_Add(a, b); -> 在 exception 中执行了loggingAttribute.OnException(methodContext); -> 在 finally中执行了 loggingAttribute.OnSuccess(methodContext); 和 loggingAttribute.OnExit(methodContext);

注:do{}while(bool) 执行了$Rougamo_Add(a, b); 是因为 Rougamo 可以实现方法执行失败重试功能

至此我们明白了 Rougamo 实现 Aop功能是通过编译时修改IL代码,往代码增加对应的生命周期代码。那他为什么可以做到呢?其实是借用了Fody ->  Mono.Cecil 的方式。

代码如下:https://gitee.com/Karl_Albright/csharp-demo/tree/master/FodyDemo/RougamoDemo

 

2. Fody ->  Mono.Cecil 

Fody 是一个开源项目,github: https://github.com/Fody/Fody,相关教程文档在 https://github.com/Fody/Home/tree/master/pages

创建类库,选择netstandard2.0,命名为HelloWorld,Nuget安装 Fody 和 FodyPackaging

注:必须创建 netstandard2.0,因为FodyPackaging的目标是netstandard2.0,

 在HelloWorld项目中,我们只放 HWAttribute类,继承于 Attribute。代码如下

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property)]
public class HWAttribute : Attribute
{}

 

再次创建类库,选择netstandard2.0,命名为HelloWorld.Fody,Nuget安装 FodyHelpers,引用HelloWorld类库

在HelloWorld.Fody项目中,我们只放ModuleWeaver类(类名是固定的,详情见Fody文档),继承于 BaseModuleWeaver。代码如下

using Fody;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;namespace HelloWorld.Fody
{public partial class ModuleWeaver : BaseModuleWeaver{public override void Execute(){foreach (var type in ModuleDefinition.Types){foreach (var method in type.Methods){var customerAttribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(HWAttribute));if (customerAttribute != null){ProcessMethod(method);}}}}public override IEnumerable<string> GetAssembliesForScanning(){yield return "mscorlib";yield return "System";}private MethodInfo _writeLineMethod => typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });private void ProcessMethod(MethodDefinition method){// 获取当前方法体中的第一个IL指令var processor = method.Body.GetILProcessor();var current = method.Body.Instructions.First();// 插入一个 Nop 指令,表示什么都不做var first = Instruction.Create(OpCodes.Nop);processor.InsertBefore(current, first);current = first;// 构造 Console.WriteLine("Hello World")foreach (var instruction in GetInstructions(method)){processor.InsertAfter(current, instruction);current = instruction;}}private IEnumerable<Instruction> GetInstructions(MethodDefinition method){yield return Instruction.Create(OpCodes.Nop);yield return Instruction.Create(OpCodes.Ldstr, "Hello World.");yield return Instruction.Create(OpCodes.Call, ModuleDefinition.ImportReference(_writeLineMethod));}}
}

在代码中,我们遍历了所有类型的所有方法,如果方法标注了 HWAttribute特性,则增加 Console.WriteLine("Hello World."); 代码。

 

创建控制台应用程序,命名为HelloWorldFodyDemo,添加 HelloWorld 和 HelloWorld.Fody 项目引用,并且手动增加 WeaverFiles标签,目标是HelloWorld.Fody.dll

 在控制台中,我们需要一个方法,方法上有 HWAttribute 特性就可以了,代码如下

internal class Program
{static void Main(string[] args){Echo();Console.ReadKey();}[HW]public static void Echo(){Console.WriteLine("Hello Fody.");}
}

在控制台项目中,我们还需要 FodyWeavers.xml 和 FodyWeavers.xsd 文件,(我也是从上面Rougamo项目中复制的),内容如下

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"><HelloWorld />
</Weavers>
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="Weavers"><xs:complexType><xs:all><xs:element name="HelloWorld" minOccurs="0" maxOccurs="1" type="xs:anyType" /></xs:all><xs:attribute name="VerifyAssembly" type="xs:boolean"><xs:annotation><xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation></xs:annotation></xs:attribute><xs:attribute name="VerifyIgnoreCodes" type="xs:string"><xs:annotation><xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation></xs:annotation></xs:attribute><xs:attribute name="GenerateXsd" type="xs:boolean"><xs:annotation><xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation></xs:annotation></xs:attribute></xs:complexType></xs:element>
</xs:schema>

目前,文件结构如下

FodyDemo
|--- HelloWorld|--- HWAttribute.cs|--- HelloWorld.csproj
|--- HelloWorld.Fody|--- HelloWorld.Fody.csproj|--- ModuleWeaver.cs
|--- HelloWorldFodyDemo|--- FodyWeavers.xml|--- FodyWeavers.xsd|--- HelloWorldFodyDemo.csproj|--- Program.cs

代码如下:https://gitee.com/Karl_Albright/csharp-demo/tree/master/FodyDemo

最后运行结果如下,很明显,HWAttribute生效了,我们成功的在Echo()方法前打印了Hello World。

 我们再次打开ILSpy工具,得到的结果如图,代码增加了Console.WriteLine("Hello World.");行代码

 4. Fody 有很多其他的“插件”,大家可以多试试

AutoProperties.Fody: 这个外接程序为您提供了对自动属性的扩展控制,比如直接访问backing字段或拦截getter和setter。

PropertyChanged.Fody: 将属性通知添加到实现INotifyPropertyChanged的所有类。

InlineIL.Fody: 在编译时注入任意IL代码。

MethodDecorator.Fody:通过IL重写编译时间装饰器模式。

NullGuard.Fody: 将空参数检查添加到程序集。

ToString.Fody: 给属性生成ToString()方法

Rougamo.Fody: 在编译时生效的AOP组件,类似于PostSharp。

 

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

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

相关文章

WhaleStudio 2.6正式发布,WhaleTunnel同步性能与连接器数量再创新高!

在这个数据驱动的大模型时代,数据集成的作用和意义愈发重要。数据不仅仅是信息的载体,更是推动企业决策和创新的关键因素。作为全球最流行的批流一体数据集成工具,WhaleTunnel随着WhaleStudio 2.6版本正式发布,带来了多项功能增强和新特性,性能大幅提升,连接器和功能方面…

代码随想录算法训练营第四十二天 | 1049最后一块石头的重量II 494.目标和 474.一和零

1049.最后一块石头的重量 题目链接 文章讲解 视频讲解解题思路:将石头尽量分为相等的两堆,两堆最差即为所求结果石头的重量就是石头的价值动规五部曲:dp[j]:表示背包容量为j时可以装的石头的总价值 递推公式:dp[j] = max(dp[j], dp[j-stones[i]] + stones[i] 初始化:均初…

Apache DolphinScheduler社区又一PMC获推选通过!

PROFILE姓名:程鑫 公司:阿里云 职位:开发工程师 Github ID: rickchengx 从事领域:大数据调度系统开发 兴趣爱好:健身推举理由 他于2022年8月2日开始了他的DolphinScheduler之旅,在社区工作了将近两年,并于2023年5月12日成为Committer。成为Committer后的一年里,他继续保…

BOSHIDA 探讨DC/AC电源模块为绿色能源应用提供可靠的转换解决方案

BOSHIDA 探讨DC/AC电源模块为绿色能源应用提供可靠的转换解决方案 DC/AC电源模块是一种能够将直流电源转换为交流电源的装置。随着绿色能源的不断发展和应用,DC/AC电源模块在可再生能源、电动车辆、太阳能发电等领域中扮演着重要的角色。本文将着重探讨DC/AC电源模块为绿色能源…

Centos7 安装Rabbitmq3.9.11

安装erlang 安装依赖包yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel下载wget https://github.com/erlang/otp/releases/download/OTP-24.1.7/otp_src_24.1.7.tar.gz解压tar -zxvf otp_src_24.1.7.tar.gz转移到…

mysql数据库简介

一、数据库介绍 1.数据库基本概念 数据(Data) 描述事物的符号记录 包括数字,文字、图形、图像、声音、档案记录等 以“记录”形式按统一的格式进行存储 表 将不同的记录组织在一起 用来存储具体数据 数据库 表的集合,是存储数据的仓库 以一定的组织方式存储的相互有关的数据…

mac 电脑查看已安装的谷歌插件

1、打开谷歌浏览器 地址栏上面输入  chrome://version/ 2、找到 个人资料路径: /Users/admin/Library/Application Support/Google/Chrome/Default3、打开个新窗口 地址了上输入:个人资料路径 ,并找到 Extensions/ 文件夹 4、里面都是已安装的谷歌插件, 想要哪个插件…

Nordic nRF Connect SDK(NCS) VS Code 安装记录

1.Nordic SDK Nordic有2套并存的SDK:老的nRF5 SDK和新的nRF Connect SDK(NCS),两套SDK相互独立,大家选择其中一套进行开发即可。 一般而言,如果你选择的芯片是nRF51或者nRF52系列,那么推荐使用nRF5 SDK。 如果你选择的是Nordic最新产品系列,比如nRF53或者nRF9160,那么…

浅谈 K8s Service 网络机制

浅谈 K8s Service 网络机制 云原生运维圈 2024-07-01 12:03 上海 1人听过以下文章来源于腾讯云原生 ,作者王成腾讯云原生. 云原生技术交流阵地,汇聚云原生最新技术资讯、文章、活动,以及云原生产品及用户最佳实践内容。王成,腾讯云研发工程师,Kubernetes member,从事数据…

全新升级!中央集中式架构功能测试为新车型保驾护航

目前,文中所述功能测试新方案均已应用于国内多款新架构车型的研发,得到了广泛认可。 “软件定义汽车”新时代下,整车电气电气架构向中央-区域集中式发展已成为行业共识,车型架构的变革带来更复杂的整车功能定义、更多的新技术的应用(如SOA服务化、智能配电等)和更…

江门MES制造执行系统:助力工厂实现智能化管理

江门MES制造执行系统(MES)在工厂实现智能化管理方面发挥着重要作用,以下是它的一些助力方面: 实时监控与控制:江门MES系统可以实时监控生产过程中的各个环节,包括设备状态、生产进度、质量指标等,帮助工厂管理人员及时了解生产情况并做出相应的调整和控制。生产计划与排程…

搭建微信小程序

在开发小程序之前,您需要先注册微信小程序。进入小程序页面,单击前往注册,根据指引填写信息和提交相应的资料,点击注册,完成账号申请。使用申请的微信公众平台账号登录小程序后台,单击开发管理> 开发设置,可以看到小程序的AppID,请记录AppID,后续操作中需要使用。 …

《从零开始学Python》(第二版) PDF读书分享

Python 是一种面向对象、解释型计算机程序设计语言,由 Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 1991 年。Python 语法简洁而清晰,具有丰富和强大的类库。它常被昵称为胶水语言,能够把用其他语言制作的各种模块(尤其是 C/C++)很轻松地联结在一起。 Pyt…

记一次 .NET某网络边缘计算系统 卡死分析

一:背景 1. 讲故事 早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ?网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数…

Cannot load from short array because sun.awt.FontConfiguration.head is null

新服务器,部署项目,使用easyExcel功能导出文件时,报错提示:Cannot load from short array because "sun.awt.FontConfiguration.head" is null,可以看到是字体文件配置引发的空指针异常; 解决方法:登录服务器,执行命令 yum install fontconfigfc-cache --for…

heap

堆块: chunk 堆是以一个个的堆块构成的,这些堆块就叫chunk chunk的大小是8字节对齐,但是一个堆块的具体大小是16字节对齐的,比如一个堆块只能是 0x40,0x50,0x60 不会是0x48这样的数据 其中一个堆块的header头部字节占16字节大小,也就是0x10字节 64位程序下的最小长度是3…

固件的提取

固件提取的三类方法:直接从官网上找到目标型号的设备固件下载 使用Telnet或者ssh从目标设备中获取固件 从开发板中的flash芯片中或者通过uart和jtag调试接口将固件提取下来JTAG(Joint Test Action Group),是一种用于测试和调试电子设备的技术标准。它使用4线或5线接口,其中…

转:在Linux上运行WinForm

C#winform软件实现一次编译,跨平台windows和linux、mac兼容运行,兼容Visual Studio原生界面Form表单开发 - 亲善美 - 博客园 (cnblogs.com)一、背景: 微软的.net core开发工具,目前来看,winform界面软件还没有打算要支持linux系统下运行的意思,要想让c#桌面软件在linux系…

固件的烧录以及部分PCB基础

固件 固件的基础定义: 固件(firmware)一般存储于设备中的电可擦除只读存储器(允许用户通过特定的电子方式复写存储内容,在【工作情况下是只读的,并且关闭电源仍存储数据)EEPROM(Electrically Erasable Programmable ROM)或FLASH芯片中,一般可由用户通过特定的刷新程序进…

Golang:go-querystring将struct编码为URL查询参数的库

Golang:go-querystring将struct编码为URL查询参数的库 原创 吃个大西瓜 Coding Big Tree 2024-05-09 08:30 北京go-querystring is a Go library for encoding structs into URL query parameters.译文:go-querystring 将struct编码为URL查询参数的Golang库文档https://pkg.g…