zoukankan      html  css  js  c++  java
  • 日志系统实战(二)-AOP动态获取运行时数据

    介绍

    这篇距上一篇已经拖3个月之久了,批评自己下。

    通过上篇介绍了解如何利用mono反射代码,可以拿出编译好的静态数据、例如方法参数信息之类的。

    但实际情况是往往需要的是运行时的数据,就是用户输入等外界的动态数据。

    既然是动态的,那就是未知的,怎么通过提前注入的代码获取呢!

    阅读目录:

    1. 普通写法
    2. 注入定义
    3. Weave函数
    4. 参数构造
    5. 业务编写
    6. 注入调用

    普通写法

     public static string GetPoint(int x, int y)
     {
        var value=x;
    }
    

    动态获取和普通这样写代码是一样的,只需要把注入的代码,生成一个同样的接收变量就可以了。 

    就像上面value 样接收,然后传递给记录的函数就可以了。

    注入定义

      public class WeaveService : Attribute
        {
        }
        public class WeaveAction : Attribute
        {
        }
        public class Log : WeaveAction
        {
            public static void OnActionBefore(MethodBase mbBase, object[] args)
            {
                for (int i = 0; i < args.Length; i++)
                {
                    Console.WriteLine(string.Format("{0}方法,第{1}参数是:{2}",mbBase.Name,i, args[i]));
                }
            }
        }

    WeaveService WeaveAction 2个Attribute是注入的标记,方便在注入查找快速定位。

    OnActionBefore是接收函数,arg就是函数运行时的参数。

    Weave函数

    这块代码在上篇已经有过注释了,这里不在多做描述。

     public static void Weave(string[] assemblyPath)
            {
                foreach (var item in assemblyPath)
                {
                    var assembly = AssemblyDefinition.ReadAssembly(item);
    
                    var types = assembly.MainModule.Types.Where(n => n.CustomAttributes.Any(y => y.AttributeType.Resolve().Name == "WeaveService"));
    
                    foreach (var type in types)
                    {
                        foreach (var method in type.Methods)
                        {
                            var attrs =
                                method.CustomAttributes.Where(y => y.AttributeType.Resolve().BaseType.Name == "WeaveAction");
                            foreach (var attr in attrs)
                            {
                                var resolve = attr.AttributeType.Resolve();
                                var ilProcessor = method.Body.GetILProcessor();
                                var firstInstruction = ilProcessor.Body.Instructions.First();
                                var onActionBefore = resolve.GetMethods().Single(n => n.Name == "OnActionBefore");
                                var mfReference = assembly.MainModule.Import(typeof(System.Reflection.MethodBase).GetMethod("GetCurrentMethod"));
                                ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, mfReference));
    
                                MakeArrayOfArguments(method, firstInstruction, ilProcessor, 0, method.Parameters.Count, assembly);
                                ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, onActionBefore));
                            }
                        }
                    }
                    if (types.Any())
                    {
                        assembly.Write(item);
                    }
                }
            }

    参数构造

    动态获取函数参数的函数,代码有详细注释。

     1    /// <summary>
     2         /// 构建函数参数
     3         /// </summary>
     4         /// <param name="method">要注入的方法</param>
     5         /// <param name="firstInstruction">函数体内第一行指令认 IL_0000: nop</param>
     6         /// <param name="writer">mono IL处理容器</param>
     7         /// <param name="firstArgument">默认第0个参数开始</param>
     8         /// <param name="argumentCount">函数参数的数量,静态数据可以拿到</param>
     9         /// <param name="assembly">要注入的程序集</param>
    10         public static void MakeArrayOfArguments(MethodDefinition method, Instruction firstInstruction, ILProcessor writer, int firstArgument,
    11                                           int argumentCount, AssemblyDefinition assembly)
    12         {
    13             //实例函数第一个参数值为this(当前实例对象),所以要从1开始。
    14             int thisShift = method.IsStatic ? 0 : 1;
    15 
    16             if (argumentCount > 0) 
    17             {
    18                 //我们先创建个和原函数参数,等长的空数组。
    19                 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, argumentCount - firstArgument));
    20                 //然后实例object数组,赋值给我们创建的数组
    21                 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Newarr,
    22                                             assembly.MainModule.Import(typeof(object))));
    23 
    24                 //c#代码描述
    25                 //object[] arr=new object[argumentCount - firstArgument] 
    26                 for (int i = firstArgument; i < argumentCount; i++)  //遍历参数
    27                 {
    28                     var parameter = method.Parameters[i];
    29 
    30                     //在堆栈上复制一个值
    31                     writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Dup));
    32                     //将常量 i - firstArgument 进行压栈,数组[i - firstArgument] 这个东东。
    33                     writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldc_I4, i - firstArgument));
    34                     //将第i + thisShift个参数 压栈。  
    35                     writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldarg, (short)(i + thisShift)));
    36                     //装箱成object
    37                     ToObject(assembly, firstInstruction, parameter.ParameterType, writer);
    38                     //压栈给数组 arr[i]赋值
    39                     writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Stelem_Ref));
    40 
    41                     //c#代码描述
    42                     // arr[i]=value;
    43                 }
    44             }
    45             else
    46             {
    47                 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Ldnull));
    48             }
    49         }
    50         public static void ToObject(AssemblyDefinition assembly, Instruction firstInstruction, TypeReference originalType, ILProcessor writer)
    51         {
    52             if (originalType.IsValueType)
    53             {
    54                 //普通值类型进行装箱操作
    55                 writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, originalType));
    56             }
    57             else
    58             {
    59                 if (originalType.IsGenericParameter)
    60                 {
    61                     //集合装箱
    62                     writer.InsertBefore(firstInstruction, writer.Create(OpCodes.Box, assembly.MainModule.Import(originalType)));
    63                 }
    64 
    65             }
    66         }

    介绍下mono InsertBefore这个函数,这个函数是在某个指令之前插入指令。

    通过上图看出,第一行指令是IL_0000: nop 。 第一行追加了 ldc.i4 2 指令,第二行我们还是nop 之前追加。 自上而下

    业务编写

    定义个要注入的用户类,然后标记下。

      [WeaveService]
        public static class UserManager
        {
    
            [Log]
            public static string GetUserName(int userId, string memberid)
            {
                return "成功";
            }
            [Log]
            public static string GetPoint(int x, int y)
            {
                var sum = x + y;
    
                return "用户积分: " + sum;
            }
        }

    平常的业务写法,不需要增加多余的代码。

     public static void Main(string[] args)
            {
              
                UserManager.GetUserName(1,"v123465");
         
                UserManager.GetPoint(2, 3);
    
                Console.ReadLine();
            }

    注入调用

    把业务类编译输入到D盘test目录下,用前面的Weave函数对Test.exe进行注入,即分析Test.exe编译生成的IL代码,添加额外的代码段。

      CodeInject.Weave(new string[] { @"D:	estTest.exe" });

    运行结果如下

    反编译后的c#

    总结 

    通过静态注入,能使我们更好的从实际用途上去了解IL语言。

    拿到动态数据仅仅抛砖引玉,利用Mono可以写自己的AOP静态组件。

    参考资源

    postsharp源码

    http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes_fields(v=vs.110).aspx

  • 相关阅读:
    java实现打印倒直角三角形
    java实现打印倒直角三角形
    java实现打印倒直角三角形
    java实现打印直角三角形
    java实现打印直角三角形
    java实现打印直角三角形
    计算一个班的平均分
    计算一个班的平均分
    计算一个班的平均分
    Creating a Message Queue in PHP Without External Libraries
  • 原文地址:https://www.cnblogs.com/mushroom/p/4124878.html
Copyright © 2011-2022 走看看