经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
Rougamo、Fody 实现静态Aop
来源:cnblogs  作者:Karl_Albright  时间:2024/7/1 14:35:51  对本文有异议

最近在看项目,看到别人使用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

  1. [AttributeUsage(AttributeTargets.Method)]
  2. public class LoggingAttribute : MoAttribute
  3. {
  4. public override void OnEntry(MethodContext context)
  5. {
  6. Console.WriteLine("执行方法 {0}() 开始,参数:{1}.", context.Method.Name,
  7. JsonConvert.SerializeObject(context.Arguments));
  8. }
  9. public override void OnException(MethodContext context)
  10. {
  11. Console.WriteLine("执行方法 {0}() 异常,{1}.", context.Method.Name, context.Exception.Message);
  12. }
  13. public override void OnExit(MethodContext context)
  14. {
  15. Console.WriteLine("执行方法 {0}() 结束.", context.Method.Name);
  16. }
  17. public override void OnSuccess(MethodContext context)
  18. {
  19. Console.WriteLine("执行方法 {0}() 成功.", context.Method.Name);
  20. }
  21. }
  22. internal class Program
  23. {
  24. static void Main(string[] args)
  25. {
  26. Add(1, 2);
  27. AddAsync(1, 2);
  28. Divide(1, 2);
  29. }
  30. [Logging]
  31. static int Add(int a, int b) => a + b;
  32. [Logging]
  33. static Task<int> AddAsync(int a, int b) => Task.FromResult(a + b);
  34. [Logging]
  35. static decimal Divide(decimal a, decimal b) => a / b;
  36. }

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

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

下面是运行结果

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

  1. internal class Program
  2. {
  3. private static void Main(string[] args)
  4. {
  5. Add(1, 2);
  6. AddAsync(1, 2);
  7. Divide(1m, 2m);
  8. }
  9. [DebuggerStepThrough]
  10. private static int Add(int a, int b)
  11. {
  12. LoggingAttribute loggingAttribute = new LoggingAttribute();
  13. IMo[] mos = new IMo[1] { loggingAttribute };
  14. 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 });
  15. loggingAttribute.OnEntry(methodContext);
  16. int result = default(int);
  17. if (methodContext.ReturnValueReplaced)
  18. {
  19. result = (int)methodContext.ReturnValue;
  20. loggingAttribute.OnExit(methodContext);
  21. return result;
  22. }
  23. if (methodContext.RewriteArguments)
  24. {
  25. a = (int)methodContext.Arguments[0];
  26. b = (int)methodContext.Arguments[1];
  27. }
  28. bool flag = default(bool);
  29. do
  30. {
  31. try
  32. {
  33. while (true)
  34. {
  35. try
  36. {
  37. flag = false;
  38. result = $Rougamo_Add(a, b);
  39. }
  40. catch (Exception exception)
  41. {
  42. methodContext.Exception = exception;
  43. methodContext.Arguments[0] = a;
  44. methodContext.Arguments[1] = b;
  45. loggingAttribute.OnException(methodContext);
  46. if (methodContext.RetryCount > 0)
  47. {
  48. continue;
  49. }
  50. if (methodContext.ExceptionHandled)
  51. {
  52. result = (int)methodContext.ReturnValue;
  53. break;
  54. }
  55. throw;
  56. }
  57. break;
  58. }
  59. }
  60. finally
  61. {
  62. if (methodContext.HasException || methodContext.ExceptionHandled)
  63. {
  64. goto IL_0160;
  65. }
  66. methodContext.ReturnValue = result;
  67. methodContext.Arguments[0] = a;
  68. methodContext.Arguments[1] = b;
  69. loggingAttribute.OnSuccess(methodContext);
  70. if (methodContext.RetryCount <= 0)
  71. {
  72. if (methodContext.ReturnValueReplaced)
  73. {
  74. result = (int)methodContext.ReturnValue;
  75. }
  76. goto IL_0160;
  77. }
  78. flag = true;
  79. goto end_IL_00fc;
  80. IL_0160:
  81. loggingAttribute.OnExit(methodContext);
  82. end_IL_00fc:;
  83. }
  84. }
  85. while (flag);
  86. return result;
  87. }
  88. [DebuggerStepThrough]
  89. private static Task<int> AddAsync(int a, int b)
  90. {
  91. LoggingAttribute loggingAttribute = new LoggingAttribute();
  92. IMo[] mos = new IMo[1] { loggingAttribute };
  93. 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 });
  94. loggingAttribute.OnEntry(methodContext);
  95. Task<int> result = default(Task<int>);
  96. if (methodContext.ReturnValueReplaced)
  97. {
  98. result = (Task<int>)methodContext.ReturnValue;
  99. loggingAttribute.OnExit(methodContext);
  100. return result;
  101. }
  102. if (methodContext.RewriteArguments)
  103. {
  104. a = (int)methodContext.Arguments[0];
  105. b = (int)methodContext.Arguments[1];
  106. }
  107. bool flag = default(bool);
  108. do
  109. {
  110. try
  111. {
  112. while (true)
  113. {
  114. try
  115. {
  116. flag = false;
  117. result = $Rougamo_AddAsync(a, b);
  118. }
  119. catch (Exception exception)
  120. {
  121. methodContext.Exception = exception;
  122. methodContext.Arguments[0] = a;
  123. methodContext.Arguments[1] = b;
  124. loggingAttribute.OnException(methodContext);
  125. if (methodContext.RetryCount > 0)
  126. {
  127. continue;
  128. }
  129. if (methodContext.ExceptionHandled)
  130. {
  131. result = (Task<int>)methodContext.ReturnValue;
  132. break;
  133. }
  134. throw;
  135. }
  136. break;
  137. }
  138. }
  139. finally
  140. {
  141. if (methodContext.HasException || methodContext.ExceptionHandled)
  142. {
  143. goto IL_015b;
  144. }
  145. methodContext.ReturnValue = result;
  146. methodContext.Arguments[0] = a;
  147. methodContext.Arguments[1] = b;
  148. loggingAttribute.OnSuccess(methodContext);
  149. if (methodContext.RetryCount <= 0)
  150. {
  151. if (methodContext.ReturnValueReplaced)
  152. {
  153. result = (Task<int>)methodContext.ReturnValue;
  154. }
  155. goto IL_015b;
  156. }
  157. flag = true;
  158. goto end_IL_00fc;
  159. IL_015b:
  160. loggingAttribute.OnExit(methodContext);
  161. end_IL_00fc:;
  162. }
  163. }
  164. while (flag);
  165. return result;
  166. }
  167. [DebuggerStepThrough]
  168. private static decimal Divide(decimal a, decimal b)
  169. {
  170. LoggingAttribute loggingAttribute = new LoggingAttribute();
  171. IMo[] mos = new IMo[1] { loggingAttribute };
  172. 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 });
  173. loggingAttribute.OnEntry(methodContext);
  174. decimal result = default(decimal);
  175. if (methodContext.ReturnValueReplaced)
  176. {
  177. result = (decimal)methodContext.ReturnValue;
  178. loggingAttribute.OnExit(methodContext);
  179. return result;
  180. }
  181. if (methodContext.RewriteArguments)
  182. {
  183. a = (decimal)methodContext.Arguments[0];
  184. b = (decimal)methodContext.Arguments[1];
  185. }
  186. bool flag = default(bool);
  187. do
  188. {
  189. try
  190. {
  191. while (true)
  192. {
  193. try
  194. {
  195. flag = false;
  196. result = $Rougamo_Divide(a, b);
  197. }
  198. catch (Exception exception)
  199. {
  200. methodContext.Exception = exception;
  201. methodContext.Arguments[0] = a;
  202. methodContext.Arguments[1] = b;
  203. loggingAttribute.OnException(methodContext);
  204. if (methodContext.RetryCount > 0)
  205. {
  206. continue;
  207. }
  208. if (methodContext.ExceptionHandled)
  209. {
  210. result = (decimal)methodContext.ReturnValue;
  211. break;
  212. }
  213. throw;
  214. }
  215. break;
  216. }
  217. }
  218. finally
  219. {
  220. if (methodContext.HasException || methodContext.ExceptionHandled)
  221. {
  222. goto IL_0160;
  223. }
  224. methodContext.ReturnValue = result;
  225. methodContext.Arguments[0] = a;
  226. methodContext.Arguments[1] = b;
  227. loggingAttribute.OnSuccess(methodContext);
  228. if (methodContext.RetryCount <= 0)
  229. {
  230. if (methodContext.ReturnValueReplaced)
  231. {
  232. result = (decimal)methodContext.ReturnValue;
  233. }
  234. goto IL_0160;
  235. }
  236. flag = true;
  237. goto end_IL_00fc;
  238. IL_0160:
  239. loggingAttribute.OnExit(methodContext);
  240. end_IL_00fc:;
  241. }
  242. }
  243. while (flag);
  244. return result;
  245. }
  246. [Logging]
  247. private static int $Rougamo_Add(int a, int b)
  248. {
  249. return a + b;
  250. }
  251. [Logging]
  252. private static Task<int> $Rougamo_AddAsync(int a, int b)
  253. {
  254. return Task.FromResult(a + b);
  255. }
  256. [Logging]
  257. private static decimal $Rougamo_Divide(decimal a, decimal b)
  258. {
  259. return a / b;
  260. }
  261. }

从实际运行的代码我们可以看到,原先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。代码如下

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

 

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

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

  1. using Fody;
  2. using Mono.Cecil;
  3. using Mono.Cecil.Cil;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Reflection;
  8. namespace HelloWorld.Fody
  9. {
  10. public partial class ModuleWeaver : BaseModuleWeaver
  11. {
  12. public override void Execute()
  13. {
  14. foreach (var type in ModuleDefinition.Types)
  15. {
  16. foreach (var method in type.Methods)
  17. {
  18. var customerAttribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(HWAttribute));
  19. if (customerAttribute != null)
  20. {
  21. ProcessMethod(method);
  22. }
  23. }
  24. }
  25. }
  26. public override IEnumerable<string> GetAssembliesForScanning()
  27. {
  28. yield return "mscorlib";
  29. yield return "System";
  30. }
  31. private MethodInfo _writeLineMethod => typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
  32. private void ProcessMethod(MethodDefinition method)
  33. {
  34. // 获取当前方法体中的第一个IL指令
  35. var processor = method.Body.GetILProcessor();
  36. var current = method.Body.Instructions.First();
  37. // 插入一个 Nop 指令,表示什么都不做
  38. var first = Instruction.Create(OpCodes.Nop);
  39. processor.InsertBefore(current, first);
  40. current = first;
  41. // 构造 Console.WriteLine("Hello World")
  42. foreach (var instruction in GetInstructions(method))
  43. {
  44. processor.InsertAfter(current, instruction);
  45. current = instruction;
  46. }
  47. }
  48. private IEnumerable<Instruction> GetInstructions(MethodDefinition method)
  49. {
  50. yield return Instruction.Create(OpCodes.Nop);
  51. yield return Instruction.Create(OpCodes.Ldstr, "Hello World.");
  52. yield return Instruction.Create(OpCodes.Call, ModuleDefinition.ImportReference(_writeLineMethod));
  53. }
  54. }
  55. }

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

 

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

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

  1. internal class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. Echo();
  6. Console.ReadKey();
  7. }
  8. [HW]
  9. public static void Echo()
  10. {
  11. Console.WriteLine("Hello Fody.");
  12. }
  13. }

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

  1. <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  2. <HelloWorld />
  3. </Weavers>
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  3. <xs:element name="Weavers">
  4. <xs:complexType>
  5. <xs:all>
  6. <xs:element name="HelloWorld" minOccurs="0" maxOccurs="1" type="xs:anyType" />
  7. </xs:all>
  8. <xs:attribute name="VerifyAssembly" type="xs:boolean">
  9. <xs:annotation>
  10. <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
  11. </xs:annotation>
  12. </xs:attribute>
  13. <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
  14. <xs:annotation>
  15. <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
  16. </xs:annotation>
  17. </xs:attribute>
  18. <xs:attribute name="GenerateXsd" type="xs:boolean">
  19. <xs:annotation>
  20. <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
  21. </xs:annotation>
  22. </xs:attribute>
  23. </xs:complexType>
  24. </xs:element>
  25. </xs:schema>

目前,文件结构如下

  1. FodyDemo
  2. |--- HelloWorld
  3. |--- HWAttribute.cs
  4. |--- HelloWorld.csproj
  5. |--- HelloWorld.Fody
  6. |--- HelloWorld.Fody.csproj
  7. |--- ModuleWeaver.cs
  8. |--- HelloWorldFodyDemo
  9. |--- FodyWeavers.xml
  10. |--- FodyWeavers.xsd
  11. |--- HelloWorldFodyDemo.csproj
  12. |--- 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。

 

原文链接:https://www.cnblogs.com/KarlAlbright/p/18277740

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号