日志系統實戰(二)-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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM