ETJava Beta | Java    注册   登录
  • 搜索:
  • Rougamo、Fody 实现静态Aop

    发表于      阅读(1)     博客类别:Crawler     转自: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。