用 Roslyn 作個 JIT 的 AOP

0. 前言

上接:AOP有幾種實現方式 java

接下來講說怎麼作AOP的demo,先用csharp 說下動態編織和靜態編織,有時間再說點java的對應內容。git

第一篇先說Roslyn 怎麼作個JIT的AOP demo。github

爲啥這樣講呢?api

實際是由於Roslyn 已經包含了JIT的所有部分,那我也就不用說任何JIT的實現了。(真爽)框架

因此本篇實際說的是如下這些內容:async

怎麼引入Roslyn作JIT編譯代碼ide

代理模式的AOP是什麼樣測試

爲何不推薦在生產環境不作優化就這樣玩?優化

1. JIT編譯代碼

Roslyn 是.NET的編譯器,感興趣的能夠參見文檔 https://docs.microsoft.com/en...ui

實際上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/Aop...

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。

因此你們的貓主子會不會跟着別人跑掉就看你們信心和手段了。

相關文章
相關標籤/搜索