1. 程式人生 > >用 Roslyn 做個 JIT 的 AOP

用 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包 ``` ``` ## 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 看起來一模一樣 ![](https://img2020.cnblogs.com/blog/818422/202012/818422-20201215124350782-194378741.png) 但兩者的真實的程式碼可能是這樣子的 ``` 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; } } ``` 所以我們呼叫的時候會是這樣 ![](https://img2020.cnblogs.com/blog/818422/202012/818422-20201215124406895-2103216355.png) ## 2.2 做一個Proxy程式碼生成器 那麼我們來做一個上面例子中能生成一模一樣的ProxyClass 程式碼生成器 首先,我們都知道 csharp 再執行中可以反射獲取元資料(反編譯出程式碼也可以做,就是我們殺雞用牛刀呢?) 我們知道了元資料,就可以拼字串拼出我們想要的程式碼(對,你沒看錯,我們拼字串就夠了) 廢話不說, show you code ``` public class ProxyGenerator { public string GenerateProxyCode(Type type