用 Roslyn 做个 JIT 的 AOP

用 Roslyn 做个 JIT 的 AOP,第1张

概述0. 前言 上接:AOP有几种实现方式 接下来说说怎么做AOP的demo,先用csharp 说下动态编织和静态编织,有时间再说点java的对应内容。 第一篇先说Roslyn 怎么做个JIT的AOP d 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" });    }}

运行结果:

HiJHi,joker!

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

2. 用代理方式实现AOP2.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,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> 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) => { },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),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。

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

总结

以上是内存溢出为你收集整理的用 Roslyn 做个 JIT 的 AOP全部内容,希望文章能够帮你解决用 Roslyn 做个 JIT 的 AOP所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1213369.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存