日志系统实战(二)-AOP动态获取运行时数据

介绍

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

通过前面一篇介绍。我们通过mono反射代码,可以拿出编译好的静态数据、例如方法参数信息之类的。但实际情况是:我更需要运行时的数据,就是用户输入等外界的动态数据。

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

其实这是一个思路的问题,下面我们具体细看下。

实现

一 普通写法

 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);
                }
            }
        }

  

三  MakeArrayOfArguments 函数。

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

 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盘,用我们前面的函数Weave进行注入。

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

运行结果如下

反编译后的c#

总结

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

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

参考资源

1:postsharp源码

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

作者:蘑菇先生

出处:http://www.cnblogs.com/mushroom/p/4124878.html

转载请保留作者和原文地址。

时间: 2024-10-10 17:22:53

日志系统实战(二)-AOP动态获取运行时数据的相关文章

使用Mono Cecil 动态获取运行时数据 (Atribute形式 进行注入) -摘自网络

目录 一:普通写法 二:注入定义 三:Weave函数 四:参数构造 五:业务编写 六:注入调用 一:普通写法 1 2 3 4 public static string GetPoint(int x, int y)  {     var value=x; } 哇 好简单啊.其实动态获取和我们普通这样写代码是一样的,我们把要注入的代码,生成一个接收的变量就可以了. 就像上面value 一样接收,然后传递给我们自己函数就可以了. 二 :注入定义 public class WeaveService :

日志系统实战(一)-AOP静态注入

背景 近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息.这时候就想到用Aop的方式了. 技术分析 AOP分动态注入和静态注入. 动态注入方式 1:Remoting的ContextAttribute上下文(性能差). 2:动态代理(反射),大多AOP框架都用这种方式. 3:MVC的filter,也是反射. 第一种:性能太差不考虑.第二种:为了记日志,生产环境都用动态代理,性能损耗不小,不推荐.第三种:只有UI层能用.其他层和第二种一样. 静态注入方式 (本文重点). 1:基于IL

java获取运行时虚拟机内存情况

/** * 获取系统内存使用情况 * * @return 包含最大内存, 使用内存, 剩余内存的map对象 */ @Override public Map getXtncSyqk() { Map map = new HashMap(); long maxMem = Runtime.getRuntime().maxMemory()/1024/1024; long freeMem = Runtime.getRuntime().freeMemory()/1024/1024; long usedMem

JVM调优系列:(二)JVM运行时数据区域

1) Method Area 2) Heap 3) Java Stacks 4) PC Registers 5) Native Method Stacks JAVA的JVM的内存模型大致可分为3个区: 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 栈区: 线程在执行一个Java方法时候,它的PC寄存器总是指向下一条需要执行的指令, 而它

关于使用动态语言运行时 (. net)

AutoCAD Managed .NET API允许您使用使用. NET 4.0 引入的动态语言运行时 (DLR). 使用DLR可以直接访问对象, 而无需: 打开一个对象进行读取或写入, 然后在完成后关闭该对象. 利用事务提交所做的更改. 在使用DLR时获得对象的ObjectId后, 可以直接访问对象的属性和方法.获得ObjectId后, 可以将ObjectId分配给数据类型的变量: Object in VB.NET dynamic in C# 获取ObjectId因对象保存到数据库的方式而异.

Javascript 笔记与总结(2-9)获取运行时的 style 对象

获取内存中(正在渲染)的 style 的值(非内联 style,obj.style 只能获得内联 style 的值),可以用 obj.currentStyle(低版本 IE 和 Opera 支持)和 window.getComputedStyle(IE9 以及 标准浏览器支持)来获取. window.getComputedStyle 的格式是 window.getComputedStyle(obj,伪元素) 第一个参数是要要获取计算后的样式的目标元素 第二个参数是期望的伪元素,如"after&q

Intermediate_JVM 20180306 : 运行时数据区域

Java比起C++一个很大的进步就在于Java不用再手动控制指针的delete与free,统一交由JVM管理,但也正因为如此,一旦出现内存溢出异常,不了解JVM,那么排查问题将会变成一项艰难的工作. Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区.这些区域都有各自的用途,以及创建销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 7>的规定(注意:我们完全可以从新的JDK1.9开始了解,但

JVM学习笔记:Java运行时数据区域

JVM执行Java程序的过程中,会使用到各种数据区域,这些区域有各自的用途.创建和销毁时间.根据<Java虚拟机规范>,JVM包括下列几个运行时数据区域,如下图所示: 其中红色部分是线程私有的,即每个线程各自都有自己的一份.绿色部分是各个线程共享的. 1.PC寄存器(The pc Register) (1)每一个Java线程都有一个PC寄存器. (2)PC寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息. (3)此内存区域是唯一一个在JV

JVM 运行时数据区详解

一.运行时数据区: Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同数据区域. 1.有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,所有的线程共享这些数据区. 2.第二种则是与线程一一对应,随线程的开始和结束而创建和销毁,线程之间相互隔离. java虚拟机所管理的内存将会包括以下几个运行时数据区域 二.数据区详解 1.程序计数器(Program Counter Register) 也叫PC寄存器是一块较小的内存空间,它的作用是存储当前线程所执行的字节码的信号指示器.