zoukankan      html  css  js  c++  java
  • 用 Roslyn 做个 JIT 的 AOP

    0. 前言

    上接:AOP有几种实现方式

    接下来说说怎么做AOP的demo,先用csharp 说下动态编织和静态编织,有时间再说点java的对应内容。

    第一篇先说Roslyn 怎么做个JIT的AOP demo。

    为啥这样讲呢?

    实际是因为Roslyn 已经包含了JIT的全部部分,那我也就不用说任何JIT的实现了。(真爽)

    所以本篇实际说的是以下这些内容:

    怎么引入Roslyn做JIT编译代码

    代理模式的AOP是什么样

    为什么不推荐在生产环境不做优化就这样玩?

    1. JIT编译代码

    Roslyn 是.NET的编译器,感兴趣的可以参见文档 https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/

    实际上Roslyn已经做的非常简单了,几行代码引入进来就可以编译csharp代码了。

    不信我们就手把手写一个

    1.1 引入Roslyn包

     <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
        <PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
      </ItemGroup>
    

    1.2 代码转化为语法树

    public SyntaxTree ParseToSyntaxTree(string code)
    {
      var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, preprocessorSymbols: new[] { "RELEASE" });
      // 有许多其他配置项,最简单这些就可以了
      return CSharpSyntaxTree.ParseText(code, parseOptions);
    }
    

    1.3 准备编译器实例

    public CSharpCompilation BuildCompilation(SyntaxTree syntaxTree)
    {
      var compilationOptions = new CSharpCompilationOptions(
        concurrentBuild: true,
        metadataImportOptions: MetadataImportOptions.All,
        outputKind: OutputKind.DynamicallyLinkedLibrary,
        optimizationLevel: OptimizationLevel.Release,
        allowUnsafe: true,
        platform: Platform.AnyCpu,
        checkOverflow: false,
        assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default);
      // 有许多其他配置项,最简单这些就可以了
      var references = AppDomain.CurrentDomain.GetAssemblies()
        .Where(i => !i.IsDynamic && !string.IsNullOrWhiteSpace(i.Location))
        .Distinct()
        .Select(i => MetadataReference.CreateFromFile(i.Location));
      // 获取编译时所需用到的dll, 这里我们直接简单一点 copy 当前执行环境的
      return CSharpCompilation.Create("code.cs", new SyntaxTree[] { syntaxTree }, references, compilationOptions);
    }
    

    1.4 编译到内存中

    public Assembly ComplieToAssembly(CSharpCompilation compilation)
    {
        using (var stream = new MemoryStream())
        {
            var restult = compilation.Emit(stream);
            if (restult.Success)
            {
                stream.Seek(0, SeekOrigin.Begin);
                return AssemblyLoadContext.Default.LoadFromStream(stream);
            }
            else
            {
                throw new Exception(restult.Diagnostics.Select(i => i.ToString()).DefaultIfEmpty().Aggregate((i, j) => i + j));
            }
        }
    }
    

    1.5 测试一下

    static void TestJIT()
    {
         var code = @"
        public class HiJ
        {
            public void Test(string v)
            {
                System.Console.WriteLine($""Hi, {v}!"");
            }
        }";
        var jit = new Jit();
        var syntaxTree = jit.ParseToSyntaxTree(code);
        var compilation = jit.BuildCompilation(syntaxTree);
        var assembly = jit.ComplieToAssembly(compilation);
        // test
        foreach (var item in assembly.GetTypes())
        {
            Console.WriteLine(item.FullName);
            item.GetMethod("Test").Invoke(Activator.CreateInstance(item), new object[] { "joker" });
        }
    }
    

    运行结果:

    HiJ
    
    Hi, joker!
    

    就这么简单,你就可以JIT了,想干什么都可以了。

    2. 用代理方式实现AOP

    2.1 回顾代理是什么

    这里单独再说一下代理是什么,

    毕竟很多AOP框架或者其他框架都有利用代理的思想,

    为什么都要这样玩呢?

    很简单,代理就是帮你做相同事情,并且可以比你做的更多,还一点儿都不动到你原来的代码。

    比如如下 真实的class 和代理class 看起来一模一样

    但两者的真实的代码可能是这样子的

    RealClass:
    
    public class RealClass
    {
      public virtual int Add(int i, int j)
      {
        return i + j;
      }
    }
    
    
    ProxyClass:
    
    public class ProxyClass : RealClass
    {
        public override int Add(int i, int j)
        {
            int r = 0;
            i += 7;
            j -= 7;
            r = base.Add(i, j);
            r += 55;
            return r;
        }
    }
    

    所以我们调用的时候会是这样

    2.2 做一个Proxy代码生成器

    那么我们来做一个上面例子中能生成一模一样的ProxyClass 代码生成器

    首先,我们都知道 csharp 再运行中可以反射获取元数据(反编译出代码也可以做,就是我们杀鸡用牛刀呢?)

    我们知道了元数据,就可以拼字符串拼出我们想要的代码(对,你没看错,我们拼字符串就够了)

    废话不说, show you code

    public class ProxyGenerator
    {
        public string GenerateProxyCode(Type type, Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall)
        {
            var sb = new StringBuilder();
            sb.Append($"{(type.IsPublic ? "public" : "")} class {type.Name}Proxy : {type.Name} {{ ");
            foreach (var method in type.GetTypeInfo().DeclaredMethods)
            {
                GenerateProxyMethod(beforeCall, afterCall, sb, method);
            }
            sb.Append(" }");
            return sb.ToString();
        }
        private static void GenerateProxyMethod(Action<StringBuilder, MethodBase> beforeCall, Action<StringBuilder, MethodBase> afterCall, StringBuilder sb, MethodInfo method)
        {
            var ps = method.GetParameters().Select(p => $"{p.ParameterType.FullName} {p.Name}");
            sb.Append($"{(method.IsPublic ? "public" : "")} override {method.ReturnType.FullName} {method.Name}({string.Join(",", ps)}) {{");
            sb.Append($"{method.ReturnType.FullName} r = default;");
            beforeCall(sb, method);
            sb.Append($"r = base.{method.Name}({string.Join(",", method.GetParameters().Select(p => p.Name))});");
            afterCall(sb, method);
            sb.Append("return r; }");
        }
    }
    

    测试一下

    public static class TestProxyGenerator
    {
        public static void Test()
        {
            var generator = new ProxyGenerator();
            var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => { }, (sb, method) => { sb.Append("r++;"); });
            Console.WriteLine(code);
        }
    }
    

    结果:

    public class RealClassProxy : RealClass { public override System.Int32 Add(System.Int32 i,System.Int32 j) {System.Int32 r = default;r = base.Add(i,j);r++;return r; } }
    

    2.3 结合在一起测试一下

    public static class TestProxyJit
    {
        public static RealClass GenerateRealClassProxy()
        {
            var generator = new ProxyGenerator();
            var code = generator.GenerateProxyCode(typeof(RealClass), (sb, method) => { }, (sb, method) => { sb.Append("r++;"); });
            var jit = new Jit();
            var syntaxTree = jit.ParseToSyntaxTree(code);
            var compilation = jit.BuildCompilation(syntaxTree);
            var assembly = jit.ComplieToAssembly(compilation);
            return Activator.CreateInstance(assembly.GetTypes().First()) as RealClass;
        }
        public static void Test()
        {
            RealClass proxy = GenerateRealClassProxy();
            var i = 5;
            var j = 10;
            Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");
        }
    }
    

    结果为:

    5 + 10 = 15, but proxy is 16
    

    是的,我们写了这么多代码就是为了让 15 变成 16 ,让别人不知道 多了个 r++; ,这就是AOP的意义

    完整的demo 放在 https://github.com/fs7744/AopDemoList

    2.4 再完善完善就可以了。。。(也就再写个几年)

    你只需要完善如下:

    • 支持 void 方法
    • 支持 async await 方法
    • 支持抽象类
    • 支持接口
    • 支持构造方法
    • 支持属性
    • 支持索引器
    • 支持 in out ref
    • 支持泛型
    • 支持嵌套类
    • 支持剩下的各种各样情况

    嗯,相信你自己,你可以的

    3. 不推荐在生产环境不经过优化就这样玩,为什么?

    3.1 两幅图

    手写的proxy :

    jit 编译proxy:

    随着需要编译的Proxy class 增多, cpu 和 内存都会一样增多
    所以要使用呢,最好用一些优化过的方案,情况会好的多,比如 https://github.com/dotnetcore/Natasha

    3.2 你信不信得过调用你api的对方

    嗯,这是一个信任度的问题,所以不要调用 Roslyn sdk 的输入暴露不做校验,黑客的骚操作是大家永远想不完的

    只要对方可信,Roslyn sdk api 别人是不会调用的哦

    但是我们都知道前人们都告诉我们有个准则:不要相信任何Input。

    所以大家的猫主子会不会跟着别人跑掉就看大家信心和手段了。

  • 相关阅读:
    verilog学习(9)实战之存储器&奇偶校验
    求职经验之综合岗位三面
    求职经验之综合岗位二面
    求职经验之综合岗位
    verilog学习(8)实战之PPL与串行/解串器
    verilog学习(7)实战之扫描链
    verilog学习(6)实战4之触发器与锁存器
    verilog学习(5)实战3之计数器与bus
    verilog学习(4)实战1之基础练习
    求职经验之器件与芯片岗
  • 原文地址:https://www.cnblogs.com/fs7744/p/14137910.html
Copyright © 2011-2022 走看看