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

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

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

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

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

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

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

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

.Net 动态代理

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

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

.NET 下动态代理技术方案

.Net框架自带

  1. 1. DispatchProxy

  2. 2. Realproxy

  3. 3. DynamicObject

开源的框架有

  1. 1. ImpromptuInterface

  2. 2. PostSharp1.5

  3. 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代码进行编码

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

    public interface IConsole
    {
        void Say();
    }

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

   var console = Activator.CreateInstance(dynamicType) as IConsole;
 
   console.Say();

动态代理简单实现

    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();
        }
    }

运行后,结果如下:

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

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

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

反编译的IL代码如下

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

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

结束

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

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

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

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

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

代码地址

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

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