Norns.Urd 中的一些設計

Norns.Urd 是什麼?

Norns.Urd 是一個基於emit實現動態代理的輕量級AOP框架.
版本基於 netstandard2.0. 因此哪些.net 版本能用你懂的。
完成這個框架的目的主要出自於我的如下意願:html

  • 靜態AOP和動態AOP都實現一次
  • 若是不實現DI,怎麼將AOP框架實現與其餘現有DI框架集成
  • 一個AOP 如何將 sync 和 async 方法同時兼容且如何將實現選擇權徹底交予用戶

但願該庫能對你們有些小小的做用
中文文檔在:https://fs7744.github.io/Norns.Urd/zh-cn/index.html
順便求個star, github 地址:https://github.com/fs7744/Norns.Urdgit

對了,若是不瞭解AOP的同窗,能夠看看這些文章:
面向切面的程序設計
什麼是面向切面編程AOP?
AOP 有幾種實現方式?github

Norns.Urd 中的一些設計

Norns.Urd的實現前提

因爲Norns.Urd的實現基於如下兩點前提編程

  1. 將 sync 和 async 方法同時兼容且如何將實現選擇權徹底交予用戶app

    • 其實這點還好,工做量變成兩倍多一些就好,sync 和 async 徹底拆分紅兩套實現。
    • 提供給用戶的Interceptor接口要提供 sync 和 async 混合在一套實現代碼的方案,畢竟不能強迫用戶實現兩套代碼,不少場景用戶不須要爲sync 和 async 的差別而實現兩套代碼
  2. 不包含任何內置DI,但要總體都爲支持DI而做框架

    • 其實若是內置DI容器可讓支持 generic 場景變得很是簡單,畢竟從DI容器中實例化對象時必須有明確的類型,可是呢,如今已經有了那麼多實現的庫了,我就不想爲了一些場景而實現不少功能(我真的懶,不然這個庫也不會寫那麼久了)
    • 可是DI容器確實解耦很是棒,我本身都經常所以受益而減小了不少代碼修改量,因此作一個aop庫必需要考慮基於DI容器作支持,這樣的話,di 支持的 open generic / 自定義實例化方法都要作支持,而且aop裏面還得提供用戶調用DI的方法,不然還很差用了 (這樣算下來,我真的偷懶了嗎?我是否是在給本身挖坑呀?)

如何設計解決的?

目前方案不必定完美,暫時算解決了問題而已 (有更好方案請必定要告訴我,我迫切須要學習)異步

提供什麼樣的攔截器編寫模式給用戶?

之前接觸一些其餘aop實現框架,不少都須要將攔截代碼分爲 方法前 / 方法後 / 有異常等等,我的以爲這樣的形式仍是必定程度上影響攔截器實現的代碼思路,總以爲不夠順滑async

可是像 ASP.NET Core Middleware就感受很是不錯,以下圖和代碼:函數

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/index/_static/request-delegate-pipeline.png?view=aspnetcore-5.0

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello, World!");
});

攔截器也應該能夠像這樣作,因此攔截器的代碼應該能夠像這樣:性能

public class ConsoleInterceptor 
{
    public async Task InvokeAsync(Context context, Delegate next)
    {
        Console.WriteLine("Hello, World!");
        await next(context);
    }
}

sync 和 async 方法如何拆分?又如何能合併在一塊兒呢?用戶有怎麼本身選擇實現sync 仍是 async 或者兩個都都實現呢?

public delegate Task AsyncAspectDelegate(AspectContext context);

public delegate void AspectDelegate(AspectContext context);

// 拆分: 
// 由AspectDelegate 和 AsyncAspectDelegate 創建兩套徹底區分 sync 和 async 的Middleware調用鏈,具體使用哪一個由具體被攔截的方法自己決定

public abstract class AbstractInterceptor : IInterceptor
{
    public virtual void Invoke(AspectContext context, AspectDelegate next)
    {
        InvokeAsync(context, c =>
        {
            next(c);
            return Task.CompletedTask;
        }).ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
    }

// 合併:
// 默認實現轉換方法內容,這樣各類攔截器均可以混在一個Middleware調用鏈中

    public abstract Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);

// 用戶自主性選擇:
// 同時提供sync 和 async 攔截器方法能夠重載,用戶就能夠本身選擇了
// 因此用戶在 async 中能夠調用專門的未異步優化代碼了,也不用說在 sync 中必須 awit 會影響性能了,
// 你認爲影響性能,你在意就本身都重載,不在意那就本身選
}

沒有內置DI,如何兼容其餘DI框架呢?

DI框架都有註冊類型,咱們能夠經過 emit 生成代理類,替換本來的註冊,就能夠作到兼容。

固然每種DI框架都須要定製化的實現一些代碼才能支持(唉,又是工做量呀)

AddTransient<IMTest>(x => new NMTest()), 相似這樣的實例化方法怎麼支持呢?

因爲這種DI框架的用法,沒法經過Func函數拿到實際會使用的類型,只能根據IMTest定義經過emit 生成 橋接代理類型,其僞碼相似以下:

interface IMTest
{
    int Get(int i);
}

class IMTestProxy : IMTest
{
    IMTest instance = (x => new NMTest())();

    int Get(int i) => instance.Get(i);
}

.AddTransient(typeof(IGenericTest<,>), typeof(GenericTest<,>)) 相似這樣的 Open generic 怎麼支持呢?

其實對於泛型,咱們經過 emit 生成泛型類型一點問題都沒有,惟一的難點是很差生成 Get<T>() 這樣的方法調用, 由於IL須要反射找到的具體方法,好比Get<int>() Get<bool>() 等等,不能是不明確的 Get<T>()

要解決這個問題就只能將實際的調用延遲到運行時調用再生成具體的調用,僞碼大體以下:

interface GenericTest<T,R>
{
    T Get<T>(T i) => i;
}

class GenericTestProxy<T,R> : GenericTest<T,R>
{
    T Get<T>(T i) => this.GetType().GetMethod("Get<T>").Invoke(i);
}
相關文章
相關標籤/搜索