Karl_Albright:Rougamo、Fody 实现静态Aop

简介: 4. Fody 有很多其他的“插件”,大家可以多试试AutoProperties.Fody: 这个外接程序为您提供了对自动属性的扩展控制,比如直接访问backing字段或拦截getter和setter。PropertyChanged.Fody: 将属性通知添加到实现INotifyPropertyChanged的所有类。InlineIL.Fody: 在编译时注入任意IL代码。MethodDecorator.Fody:通过IL重写编译时间装饰器模式。NullGuard.Fody: 将空参数检查添加到程序集。

Rougamo、Fody 实现静态Aop

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

相关文章
|
5月前
|
XML Java 程序员
Spring6框架中依赖注入的多种方式(推荐构造器注入)
依赖注入(DI)是一种过程,对象通过构造函数参数、工厂方法的参数或在对象实例构建后设置的属性来定义它们的依赖关系(即与其一起工作的其他对象)。
76 3
|
5月前
|
JSON 数据安全/隐私保护 数据格式
动态切面
`AdviceConfiguration` 类用于动态注册一个基于 AspectJ 的切面顾问,该顾问通过 `@ConditionalOnExpression` 注解控制是否生效。配置中包含一个从 Apollo 获取的 JSON 值,用于构建方法拦截器的切入点表达式,涉及特定包和类。拦截器 `ControllerAdvice` 实现了 `MethodInterceptor`,用于记录请求日志,包括 URL、参数和执行时间,同时根据配置决定是否对返回结果进行加密。
|
容器
什么是依赖注入?有哪些注入方式?
什么是依赖注入?有哪些注入方式?
136 0
|
缓存 Oracle NoSQL
【Spring学习笔记 六】静态/动态代理实现机制
【Spring学习笔记 六】静态/动态代理实现机制
101 0
|
设计模式
依赖注入和构造器注入的区别
依赖注入和构造器注入的区别
|
Java 测试技术 Spring
Spring-AOP 静态普通方法名匹配切面
Spring-AOP 静态普通方法名匹配切面
149 0
IOC创建对象方式
1.使用无参构造创建对象,默认!
|
Cloud Native Java Linux
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(下)
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(下)
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(下)
|
Java C++ Spring
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(上)
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(上)
|
Java 程序员 Spring
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(中)
static关键字有何魔法?竟让Spring Boot搞出那么多静态内部类(中)