日誌系統實戰(一)—AOP靜態注入

蘑菇先生發表於2014-08-24

背景

近期在寫日誌系統,需要在執行時在函式內注入日誌記錄,並附帶函式資訊,這時就想到用Aop注入的方式。

AOP分動態注入和靜態注入兩種注入的方式。

動態注入方式

  1. 利用Remoting的ContextBoundObject或MarshalByRefObject。
  2. 動態代理(反射),很多AOP框架都用這種方式。
  3. MVC的filter,也是反射。

第一種效能太差,必須繼承基類等,所以不考慮。

第二種為了記日誌,大量動態生成代理類,效能損耗不小,不建議生產環節推薦。

第三種MVC只能進行UI層的攔截,其他層需要實現自行實現動態攔截,跟第二種實現方式一樣。

靜態注入方式

基於Net的IL語言層級進行注入,效能損耗可以忽略不計,Net使用最多的Aop框架PostSharp採用的即是這種方式。

技術實現

宣告Attribute

public class WeaveSign:Attribute
{
}
public class WeaveAction : Attribute
{
        
}
public class Log : WeaveAction
{
        public static void OnActionBefore(MethodBase mbBase)
        {
           //mbBase 要注入方法的所有資訊
            var t = mbBase.GetParameters();
        LogManager.Record();
       }
}        

標記需要注入的方法

[Log]
public static string GetUserName(int userId)
{
        return "Vidar";
}

IL注入關鍵的地方,這裡使用Mono.Cecil進行IL分析和寫入。

public static void AutoWeave()
{
    var assemblies = BuildManager.GetReferencedAssemblies();
    var result =
    assemblies.Cast<Assembly>().Where(assembly =>!assembly.FullName.StartsWith("System.", StringComparison.OrdinalIgnoreCase));

    Weave(result);
}


private static void Weave(IEnumerable<Assembly> assemblyList)
        {
            //assemblyList要注入的程式集列表。
            foreach (var item in assemblyList)
            {
                var filepath = item.CodeBase.Substring(8, item.CodeBase.Length - 8);
                //讀取程式集
                var assembly = AssemblyDefinition.ReadAssembly(filepath);

                //獲取WeaveSign型別的型別注入標記
                var types = assembly.MainModule.Types.Where(n => n.CustomAttributes.Any(y => y.AttributeType.Resolve().Name == "WeaveSign"));

                foreach (var type in types)
                {
                    foreach (var method in type.Methods)
                    {
                        //獲取WeaveAction型別的方法注入標記
                        var attrs =
                            method.CustomAttributes.Where(y => y.AttributeType.Resolve().BaseType.Name == "WeaveAction");
                        foreach (var attr in attrs)
                        {
                            //解析型別
                            var resolve = attr.AttributeType.Resolve();
                            //獲取IL容器
                            var ilProcessor = method.Body.GetILProcessor();
                            var firstInstruction = ilProcessor.Body.Instructions.First();
                            //找到標記型別的OnActionBefore方法。
                            var onActionBefore = resolve.GetMethods().Single(n => n.Name == "OnActionBefore");
                            //建立System.Reflection.MethodBase.GetCurrentMethod()方法引用
                            var mfReference=assembly.MainModule.Import(typeof (System.Reflection.MethodBase).GetMethod("GetCurrentMethod"));

                            //注入到IL(呼叫GetCurrentMethod,入棧)
                            ilProcessor.InsertBefore(firstInstruction,
                                ilProcessor.Create(OpCodes.Call,mfReference));
                            //建立呼叫(call)標記型別的方法OnActionBefore
                            ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, onActionBefore));
                        }
                    }
                }
                if (types.Any())
                {
                    //寫入程式集
                    assembly.Write(filepath);
                }
            }
        }    

為了演示簡便,沒有在MsBuild期間進行注入,而是單寫了個測試頁面直接觸發進行程式碼注入。

  protected void Page_Load(object sender, EventArgs e)
  {
          CodeWeaver.AutoWeave();
  }

執行成功後,反編譯註入的DLL看到的IL程式碼:

  .method public hidebysig static string GetUserName(int32 userId) cil managed
{
    .custom instance void TestLibrary.Log::.ctor()
    .maxstack 1
    .locals init (
        [0] string str)
    L_0000: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetCurrentMethod()
    L_0005: call void TestLibrary.Log::OnActionBefore(class [mscorlib]System.Reflection.MethodBase)
    L_000a: nop 
    L_000b: ldstr "Vidar"
    L_0010: stloc.0 
    L_0011: br.s L_0013
    L_0013: ldloc.0 
    L_0014: ret 
}

 

C#

[Log]
public static string GetUserName(int userId)
{
    Log.OnActionBefore(MethodBase.GetCurrentMethod());
    return "Vidar";
}

 

Mono.Cecil地址 http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/

相關文章