用 Roslyn 做個 JIT 的 AOP
阿新 • • 發佈:2020-12-15
# 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