• 日志系统实战(一)—AOP静态注入


    背景

    近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用Aop注入的方式。

    AOP分动态注入和静态注入两种注入的方式。

    动态注入方式

    1. 利用Remoting的ContextBoundObject或MarshalByRefObject。
    2. 动态代理(反射),很多AOP框架都用这种方式。
    3. 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/

  • 相关阅读:
    Unix Vi 命令详解
    硬盘安装 solaris
    Oracle 10g RAC OCR 和 VotingDisk 的备份与恢复
    Unix vmstat 命令
    Unix Vi 命令详解
    How do I rename a data file
    Oracle 购买价格 和 服务费 计算方式
    Solaris 更改系统语言
    硬盘安装 solaris
    How do I rename a data file
  • 原文地址:https://www.cnblogs.com/mushroom/p/3932698.html
Copyright © 2020-2023  润新知