上接:AOP有幾種實現方式 java
接下來講說怎麼作AOP的demo,先用csharp 說下動態編織和靜態編織,有時間再說點java的對應內容。git
第一篇先說Roslyn 怎麼作個JIT的AOP demo。github
爲啥這樣講呢?api
實際是由於Roslyn 已經包含了JIT的所有部分,那我也就不用說任何JIT的實現了。(真爽)框架
因此本篇實際說的是如下這些內容:async
怎麼引入Roslyn作JIT編譯代碼ide
代理模式的AOP是什麼樣測試
爲何不推薦在生產環境不作優化就這樣玩?優化
Roslyn 是.NET的編譯器,感興趣的能夠參見文檔 https://docs.microsoft.com/en...ui
實際上Roslyn已經作的很是簡單了,幾行代碼引入進來就能夠編譯csharp代碼了。
不信咱們就手把手寫一個
<ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" /> <PackageReference Include="System.Runtime.Loader" Version="4.3.0" /> </ItemGroup>
public SyntaxTree ParseToSyntaxTree(string code) { var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, preprocessorSymbols: new[] { "RELEASE" }); // 有許多其餘配置項,最簡單這些就能夠了 return CSharpSyntaxTree.ParseText(code, parseOptions); }
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); }
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)); } } }
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了,想幹什麼均可以了。
這裏單獨再說一下代理是什麼,
畢竟不少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; } }
因此咱們調用的時候會是這樣
那麼咱們來作一個上面例子中能生成如出一轍的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; } }
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...
你只須要完善以下:
嗯,相信你本身,你能夠的
手寫的proxy :
jit 編譯proxy:
隨着須要編譯的Proxy class 增多, cpu 和 內存都會同樣增多
因此要使用呢,最好用一些優化過的方案,狀況會好的多,好比 https://github.com/dotnetcore/Natasha
嗯,這是一個信任度的問題,因此不要調用 Roslyn sdk 的輸入暴露不作校驗,黑客的騷操做是你們永遠想不完的
只要對方可信,Roslyn sdk api 別人是不會調用的哦
可是咱們都知道前人們都告訴咱們有個準則:不要相信任何Input。
因此你們的貓主子會不會跟着別人跑掉就看你們信心和手段了。