专栏首页蘑菇先生的技术笔记日志系统实战(一)—AOP静态注入

日志系统实战(一)—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/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 探索c#之不可变数据类型

    蘑菇先生
  • AutoMapper使用手册(一)

    蘑菇先生
  • 探索C#之微型MapReduce

    蘑菇先生
  • 【php设计模式】观察者模式

    当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

    码缘
  • Web前端-JavaScript基础教程上

    JavaScript是web前端开发的编程语言,大多数网站都使用到了JavaScript,所以我们要进行学习,JavaScript是必备的前端技能。

    达达前端
  • Java11震撼发布了,我们该怎么办?

    Java11已经发布了,我们今天聊聊大家还停留在哪个版本呢?大家对于新版本的迅速的发布有什么想说的呢?

    好好学java
  • JS知识总结

    var cars=new Array("Saab","Volvo","BMW"):

    阮键
  • mapboxGL之风流图

    前面的文章说到了Openlayers4中风场的实现,本文将讲述如何在mapbox GL实现类似的效果。

    lzugis
  • 纯粹依靠位操作实现整数加法运算

    Jerry Wang
  • 基于 H5与WebGL 的科幻风机 3D 展示

      许多世纪以来,风力机同水力机械一样,作为动力源替代人力、畜力,对生产力的发展发挥过重要作用。近代机电动力的广泛应用以及二十世纪50年代中东油田的发现,使风机...

    HT for Web

扫码关注云+社区

领取腾讯云代金券