日志系统实战(一)—AOP静态注入
背景
近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用Aop注入的方式。
AOP分动态注入和静态注入两种注入的方式。
动态注入方式
- 利用Remoting的ContextBoundObject或MarshalByRefObject。
- 动态代理(反射),很多AOP框架都用这种方式。
- MVC的filter,也是反射。
第一种性能太差,必须继承基类等,所以不考虑。
第二种为了记日志,大量动态生成代理类,性能损耗不小,不建议生产环节推荐。
第三种MVC只能进行UI层的拦截,其他层需要实现自行实现动态拦截,跟第二种实现方式一样。
静态注入方式
基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp采用的即是这种方式。
技术实现
声明Attribute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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(); } } |
标记需要注入的方法
1 2 3 4 5 | [Log] public static string GetUserName( int userId) { return "Vidar" ; } |
IL注入关键的地方,这里使用Mono.Cecil进行IL分析和写入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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期间进行注入,而是单写了个测试页面直接触发进行代码注入。
1 2 3 4 | protected void Page_Load( object sender, EventArgs e) { CodeWeaver.AutoWeave(); } |
运行成功后,反编译注入的DLL看到的IL代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | .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#
1 2 3 4 5 6 | [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/
作者:蘑菇先生
出处: http://mushroom.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载。未经作者同意下,必须在文章页面明显标出原文链接及作者,否则保留追究法律责任的权利。
如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?