日志系统实战(二)-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:\test\Test.exe" });

运行结果如下

反编译后的c#

总结 

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

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

参考资源

postsharp源码

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang视角下的设计模式

这篇文章想聊聊Golang语言下的设计模式问题,我觉得这个话题还是比较有意思的。Golang没有像java那样对设计模式疯狂的迷恋,而是摆出了一份“看庭前花开花...

13420
来自专栏张善友的专栏

MongoDB 聚合管道(Aggregation Pipeline)

管道概念 POSIX多线程的使用方式中, 有一种很重要的方式-----流水线(亦称为“管道”)方式,“数据元素”流串行地被一组线程按顺序执行。它的使用架构可参考...

478100
来自专栏企鹅FM

深入浅出Kotlin协程

协程(Coroutines)已经随着Kotlin1.3版本一起发布了1.0正式版,android平台可以使用如下方式引入:

5K80
来自专栏HansBug's Lab

1590: [Usaco2008 Dec]Secret Message 秘密信息

1590: [Usaco2008 Dec]Secret Message 秘密信息 Time Limit: 5 Sec  Memory Limit: 32 MB ...

36460
来自专栏叁金大数据

自学Python十 爬虫实战三

  我又来送福利啦!!!不同于上篇文章,这次我们的爬虫采用了多线程,一直以来被所谓的分布式  多线程  爬虫 给唬的怕怕的。今天就来一发多线程爬虫吧,还能看妹子...

8810
来自专栏安恒网络空间安全讲武堂

网鼎杯第一场writeup

payload:/view.php?no=-6 unIon/**/select 1,table_name,3,4 from information_schema...

24020
来自专栏非典型技术宅

Swift多线程之Operation:异步加载CollectionView图片1. Operation 设置依赖关系2. 前置知识点内容3. CollectionView中图片进行异步加载

21170
来自专栏极客猴

Django 学习笔记之模型(下)

上篇文章讲解了 Django 如何创建模型,本文将继续讲解如何对模型进行增删改查操作。

12930
来自专栏Android群英传

Android multidex 主dex是怎么来的?

44720
来自专栏跟着阿笨一起玩NET

跨线程调用窗体控件

本文转载:http://www.csharpwin.com/csharpspace/11279r6763.shtml

31910

扫码关注云+社区

领取腾讯云代金券