序言
本篇文章介紹基於Mono.Cecil實現靜態AOP的兩種方式:無交互AOP和交互式AOP。
概念介紹
Mono.Cecil:一個可加載並瀏覽現有程序集並進行動態修改並保存的.NET框架。
AOP:面向切面編程。可以簡單理解為程序中的每個類的方法均是一塊“積木”,采用AOP把新增的“積木隨心所欲地嵌入”到各個“積木”上面(前面)或下面(后面)。如下圖所示:

動態AOP:在運行時進行AOP。.NET現有.Net Remoting,Unity,Spring.NET,PostSharp,Mr Advice等多種框架可供使用。
靜態AOP:相對於動態AOP,靜態AOP是指在編譯時、運行前就已經進行了AOP。.NET中一般通過修改編譯生成的中間語言IL實現,Mono.Cecil就是實現靜態AOP一個很好的方式。根據與原有程序交互的情況,本文把靜態AOP分為無交互AOP和交互式AOP兩種方式。
無交互AOP
與原有程序無任何“交集”,純粹式的AOP。下面通過兩個例子進行說明如何通過Mono.Cecil進行無交互AOP。
同一個方法內AOP
原程序:控制台打印出“Hello World”。代碼如下:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
AOP需求:需要在打印前和打印后輸出當前時間。
-添加Mono.Cecil.dll引用並添加以下代碼
using Mono.Cecil; using Mono.Cecil.Cil;
-定位方法(建議用Linq)
AssemblyDefinition assembiy = AssemblyDefinition.ReadAssembly(Path);//Path: dll or exe Path
var method = assembiy.MainModule
.Types.FirstOrDefault(t => t.Name == "Program")
.Methods.FirstOrDefault(m => m.Name == "Main");
-獲取IL
var worker = method.Body.GetILProcessor();//Get IL
-AOP Front
string FrontTime = DateTime.Now.ToString();
var ins = method.Body.Instructions[0];//Get First IL Step
worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, FrontTime));
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
assembiy.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
-AOP Back
string BackTime = DateTime.Now.ToString();
ins = method.Body.Instructions[method.Body.Instructions.Count - 1]; //Get Lastest IL Step
worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr, BackTime));
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
assembiy.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }))));
-保存修改
assembiy.Write(Path);
-結果
采用Refactor進行對比得知AOP已成功!

“跨類”AOP
此種方式指的是在方法前后通過指定調用其他類的方法實現AOP,可用於擴展功能,如日志記錄,數據庫記錄等。
相對於同一個方法內的AOP,因為通過方法調用指定類的方法,實現更加靈活,功能擴展更加全面且在開發階段可進行調試或單元測試,所以此種方式應用層面更為廣泛。
下面將從靜態和非靜態兩種方式進行代碼實現。
原程序:控制台打印出“Hello World”
class Program { static void Main(string[] args) { Console.WriteLine("Hello World"); } }
AOP需求:需要把打印前的時間和打印后的時間記錄到數據庫中。
跨靜態類/靜態方法AOP
為示例簡便,LogDT方法表示記錄時間到數據庫中(為方便顯示,采用控制台打印的方式)
public class LogDateTime_Static { public static void LogDT() { Console.WriteLine(DateTime.Now.ToString()); } }
-AOP Front
//AOP_Front var ins = method.Body.Instructions[0];//Get First IL Step worker.InsertBefore(ins, worker.Create(OpCodes.Call, assembiy.MainModule.Import(typeof(LogDateTime).GetMethod("LogDT"))));//Call static Method
-AOP Back
//AOP_Back ins = method.Body.Instructions[method.Body.Instructions.Count - 1]; //Get Lastest IL Step worker.InsertBefore(ins, worker.Create(OpCodes.Call, assembiy.MainModule.Import(typeof(LogDateTime).GetMethod("LogDT"))));//Call static Method
-結果

跨非靜態類AOP
非靜態類實例代碼如下:
public class LogDateTime_NonStatic { public void LogDT() { Console.WriteLine(DateTime.Now.ToString()); } }
-實例化指定類
var Constructor = assembiy.MainModule.Import(typeof(LogDateTime_NonStatic).GetConstructor(new Type[] { }));//Create Instance
-AOP Front
var ins = method.Body.Instructions[0];//Get First IL Step worker.InsertBefore(ins, worker.Create(OpCodes.Newobj, Constructor)); worker.InsertBefore(ins, worker.Create(OpCodes.Call, assembiy.MainModule.Import(typeof(LogDateTime_NonStatic).GetMethod("LogDT"))));////Call Instance Method
-AOP Back
ins = method.Body.Instructions[method.Body.Instructions.Count - 1]; //Get Lastest IL Step worker.InsertBefore(ins, worker.Create(OpCodes.Newobj, Constructor)); worker.InsertBefore(ins, worker.Create(OpCodes.Call, assembiy.MainModule.Import(typeof(LogDateTime_NonStatic).GetMethod("LogDT"))));////Call Instance Method
-結果
