• .Net IL Emit 实现Aop面向切面之动态代理 案例版


    啥是Aop呢,面向切面,啥是面向切面呢,就不太好一句话说明白了。

    但是,其实不论在生活中还是工作研发中,我们都能找到它。

    在研发中,它其实就是个过滤器,每个请求来了。它都要检查一下。

    在生活中,它就是父母的谆谆教导,回家就得问你吃了么。

    在工作中,它就是打卡机,就是刷卡机,人脸识别,总得检查你一下子。

    官方解释为:面向切面编程,主要是对权限检查,日志记录,性能统计,安全控制,事务处理,异常处理,从权限检查来看,就已经很明显的了解它是干啥子的了。当然,实际用的时候,不仅仅可以检查权限。还可以搞别的。

    AOP主要的作用:从ASP.NET MVC Filter(拦截器,过滤器)的作用我们知道,拦截,一般拦截在执行方法体的前面,或者,执行方法体的返回,主要是这两部分。所以,就是从方法体执行前逻辑处理,以及在方法体执行后逻辑处理。

    .Net 动态代理

    想实现AOP,就得通过代理层来实现,那么,我们就通过动态代理来实现。

    不懂动态代理的,可以参考设计模式之代理(中介)模式即可。其实就像生活中租房子要找中介一样。

    .NET 下动态代理技术方案

    .Net框架自带

    1. DispatchProxy
    2. Realproxy
    3. DynamicObject

    开源的框架有

    1. ImpromptuInterface
    2. PostSharp1.5
    3. Castle.DynamicProxy

    实现代理的方式有两种,一种是静态织入,一种是动态织入。

    静态织入的话,相当于是在不影响业务的情况下,又对框架做了一层处理(PostSharp1.5)影响在DLL中。

    而动态织入就是在应用运行的过程中动态插入到里面,影响在运行时。

    大部分都是采用动态织入实现的。

    我这边要实现一个通用性屏蔽.NetFW和.Net Coer之间的差异,所以,不能直接用框架自带的,也不想用开源框架里的,感觉它们写的有点复杂。

    所以,得使用反射来实现这样的一个功能。

    具体作用,我是准备用在自己要写的RPC中,所以,这个主要针对接口部分做的动态代理

    具体原理如下:

    用RPC 的时候,需要公共的部分,这部分就是接口。

    啥是RPC

    RPC 全称是 Remote Procedure Call ,即远程过程调用,其对应的是我们的本地调用。远程其实指的就是需要网络通信,可以理解为调用远程机器上的方法。

    那跟HTTP,WebApi,一样,直接调用不就成了。不是的,RPC,要实现的是,调用服务端的方法,就像服务端调用自己的方法一样,有返回类型,有参数,参数类型。

    注:关于调试

    只有 fw 框架才会能保存dll查看,如果用到.NetCore项目只能直接运行,所以,想要调试,还是要在fw框架下调试
    生成的dll 可以用 反编译软件 dnSpy 进行获取。
    也可以根据dnSpy这样的反编译软件获取IL代码进行编码
    
    • 1
    • 2
    • 3

    客户端会共享这个接口,如下。

        public interface IConsole
        {
            void Say();
        }
    
    • 1
    • 2
    • 3
    • 4

    客户端调用的方式会像这样,就实现了客户端与服务端的RPC调用

       var console = Activator.CreateInstance(dynamicType) as IConsole;
     
       console.Say();
    
    • 1
    • 2
    • 3

    动态代理简单实现

        public interface IConsole
        {
            void Say();
        }
        class Program
        {
            /// <summary>
            /// .net fw项目,可以实现程序集的运行和保存,但是,.Net Core的就只能运行了
            /// </summary>
            /// <param name="args"></param>
            static void Main(string[] args)
            {
                // 在看下面的代码之前,先明白这个依赖关系,即:
                // 方法->类型->模块->程序集
    
                //定义程序集的名称
                AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
    
                // 创建一个程序集构建器
                // Framework 也可以这样:AppDomain.CurrentDomain.DefineDynamicAssembly
                AssemblyBuilder ab =
                    AssemblyBuilder.DefineDynamicAssembly(
                        aName,
                        AssemblyBuilderAccess.RunAndSave);
    
    
                // 使用程序集构建器创建一个模块构建器
                ModuleBuilder mb = ab.DefineDynamicModule(aName.Name);
    
                // 使用模块构建器创建一个类型构建器
                TypeBuilder tb = mb.DefineType("DynamicConsole");
    
                // 使类型实现IConsole接口
                tb.AddInterfaceImplementation(typeof(IConsole));
    
                var attrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig | MethodAttributes.Final;
    
                // 使用类型构建器创建一个方法构建器
                MethodBuilder methodBuilder = tb.DefineMethod("Say", attrs, typeof(void), Type.EmptyTypes);
    
                // 通过方法构建器获取一个MSIL生成器
                var IL = methodBuilder.GetILGenerator();
    
                // 开始编写方法的执行逻辑
    
                // 将一个字符串压入栈顶
                IL.Emit(OpCodes.Ldstr, "I'm here.");
    
                // 调用Console.Writeline函数
                IL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
    
                // 退出函数
                IL.Emit(OpCodes.Ret);
    
                //方法结束
    
                // 从类型构建器中创建出类型
                var dynamicType = tb.CreateType();
    
                //ab.Save(aName.Name + ".dll");
                // 通过反射创建出动态类型的实例
                var console = Activator.CreateInstance(dynamicType) as IConsole;
    
                console.Say();
                ab.Save("DynamicAssemblyExample.dll");
    
                Console.WriteLine("不错,完成了任务!");
                Console.ReadLine();
            }
        }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    运行后,结果如下:

    反编译如下:
    自己继承接口,并实现这个接口的类,然后,反射出来。

    反编译的IL代码如下

    可以看到,跟我们写的emit代码是一致的。

    结束

    至此,核心原理已经完了。

    接下来就是实际过程中的代理过程

    会让一个代理对象成为这个接口类的字段。然后在每个方法的执行过程中都会调用这个对象执行,并返回相应的结果。

    这个对象执行的过程就是调用方法到远程服务器并返回结果的过程。

    下一节,会实现一个简单的RPC 项目。

    代码地址

    https://github.com/kesshei/DynamicProxyDemo.git

    https://gitee.com/kesshei/DynamicProxyDemo.git

    文档参考

    emit 文档参考

  • 相关阅读:
    java基于 springboot+vue的校园出入管理系统 element
    运动项目记录
    哈希表(哈希函数和处理哈希冲突)_20230528
    OREPA:阿里提出训练也很快的重参数策略,内存减半,速度加倍 | CVPR 2022
    聚观早报 | OPPO开发者大会月底召开;新iPad Pro将搭载M2芯片
    软件设计师_数据结构与算法基础_学习笔记
    win10的快捷键&GPU
    TensorFlow学习笔记--(3)张量的常用运算函数
    【初阶数据结构】带头双向循环链表(C语言实现)
    ElasticSearch查询工具类分享
  • 原文地址:https://blog.csdn.net/kesshei/article/details/125331986