Mono.Cecil 初探(一):实现AOP
序言
本篇文章介绍基于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
-结果