背景
近期在寫日誌系統,需要在執行時在函式內注入日誌記錄,並附帶函式資訊,這時就想到用Aop注入的方式。
AOP分動態注入和靜態注入兩種注入的方式。
動態注入方式
- 利用Remoting的ContextBoundObject或MarshalByRefObject。
- 動態代理(反射),很多AOP框架都用這種方式。
- 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/