背景
近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用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/