<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Norns.Urd輕量級 AOP 框架

          聯(lián)合創(chuàng)作 · 2023-09-30 06:54

          Norns.Urd 是一個基于 emit 實現(xiàn)動態(tài)代理的輕量級 AOP 框架。

          版本基于 netstandard2.0. 所以哪些.net 版本能用你懂的。

          完成這個框架的目的主要出自于個人以下意愿:

          • 靜態(tài)AOP和動態(tài)AOP都實現(xiàn)一次
          • 如果不實現(xiàn)DI,怎么將AOP框架實現(xiàn)與其他現(xiàn)有DI框架集成
          • 一個AOP 如何將 sync 和 async 方法同時兼容且如何將實現(xiàn)選擇權(quán)完全交予用戶

          希望該庫能對大家有些小小的作用

          對了,如果不了解AOP的同學(xué),可以看看這些文章:

          面向切面的程序設(shè)計

          什么是面向切面編程AOP?

          AOP 有幾種實現(xiàn)方式?

          Simple Benchmark

          只是一個簡單性能測試,不代表全部場景,也沒有故意對比,

          Castle 和 AspectCore 都是非常優(yōu)秀的庫,

          Norns.Urd 很多實現(xiàn)都是參考了Castle 和 AspectCore的源碼的。

          
          BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1198 (1909/November2018Update/19H2)
          Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
          .NET Core SDK=5.0.100
            [Host]     : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
            DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
          
          
           
          Method Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
          TransientInstanceCallSyncMethodWhenNoAop 69.10 ns 1.393 ns 2.512 ns 69.70 ns 0.0178 - - 112 B
          TransientInstanceCallSyncMethodWhenNornsUrd 148.38 ns 2.975 ns 5.588 ns 145.76 ns 0.0534 - - 336 B
          TransientInstanceCallSyncMethodWhenCastle 222.48 ns 0.399 ns 0.312 ns 222.50 ns 0.0815 - - 512 B
          TransientInstanceCallSyncMethodWhenAspectCore 576.04 ns 7.132 ns 10.229 ns 573.46 ns 0.1030 - - 648 B
          TransientInstanceCallAsyncMethodWhenNoAop 114.61 ns 0.597 ns 0.499 ns 114.58 ns 0.0408 - - 256 B
          TransientInstanceCallAsyncMethodWhenNornsUrd 206.36 ns 0.937 ns 0.830 ns 206.18 ns 0.0763 - - 480 B
          TransientInstanceCallAsyncMethodWhenCastle 250.98 ns 3.315 ns 3.101 ns 252.16 ns 0.1044 - - 656 B
          TransientInstanceCallAsyncMethodWhenAspectCore 576.00 ns 4.160 ns 3.891 ns 574.99 ns 0.1373 - - 864 B

          快速入門指南

          這是一個簡單的全局AOP攔截的簡單示例,具體詳細(xì)示例代碼可以參閱Examples.WebApi

          1. 創(chuàng)建 ConsoleInterceptor.cs

             using Norns.Urd;
             using Norns.Urd.Reflection;
             using System;
             using System.Threading.Tasks;
            
             namespace Examples.WebApi
             {
                 public class ConsoleInterceptor : AbstractInterceptor
                 {
                     public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
                     {
                         Console.WriteLine($"{context.Service.GetType().GetReflector().FullDisplayName}.{context.Method.GetReflector().DisplayName}");
                         await next(context);
                     }
                 }
             }
            
          2. 設(shè)置 WeatherForecastController 的方法為 virtual

             [ApiController]
             [Route("[controller]")]
             public class WeatherForecastController : ControllerBase
             {
                 [HttpGet]
                 public virtual IEnumerable<WeatherForecast> Get() => test.Get();
             }
            
          3. AddControllersAsServices

             // This method gets called by the runtime. Use this method to add services to the container.
             public void ConfigureServices(IServiceCollection services)
             {
                 services.AddControllers().AddControllersAsServices();
             }
            
          4. 設(shè)置di 容器啟用aop 功能

             // This method gets called by the runtime. Use this method to add services to the container.
             public void ConfigureServices(IServiceCollection services)
             {
                 services.AddControllers().AddControllersAsServices();
                 services.ConfigureAop(i => i.GlobalInterceptors.Add(new ConsoleInterceptor()));
             }
            
          5. 運(yùn)行程序

            你會在控制臺看見如下輸出

             Norns.Urd.DynamicProxy.Generated.WeatherForecastController_Proxy_Inherit.IEnumerable<WeatherForecast> Get()
            

          功能說明

          Interceptor 攔截器

          在Norns.Urd中,Interceptor 攔截器是用戶可以在方法插入自己的邏輯的核心。

          攔截器結(jié)構(gòu)定義

          攔截器定義了標(biāo)準(zhǔn)結(jié)構(gòu)為IInterceptor

          public interface IInterceptor
          {
              // 用戶可以通過Order自定義攔截器順序,排序方式為ASC,全局?jǐn)r截器和顯示攔截器都會列入排序中
              int Order { get; }
          
              // 同步攔截方法
              void Invoke(AspectContext context, AspectDelegate next);
          
              // 異步攔截方法
              Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
          
              // 可以設(shè)置攔截器如何選擇過濾是否攔截方法,除了這里還有NonAspectAttribute 和全局的NonPredicates可以影響過濾
              bool CanAspect(MethodInfo method);
          }
          

          攔截器結(jié)類型

          攔截器實際從設(shè)計上只有IInterceptor這一個統(tǒng)一的定義,不過由于csharp的單繼承和Attribute的語言限制,所以有AbstractInterceptorAttribute 和 AbstractInterceptor兩個類。

          AbstractInterceptorAttribute (顯示攔截器)

          public abstract class AbstractInterceptorAttribute : Attribute, IInterceptor
          {
              public virtual int Order { get; set; }
          
              public virtual bool CanAspect(MethodInfo method) => true;
          
              // 默認(rèn)提供在同步攔截器方法中轉(zhuǎn)換異步方法為同步方式調(diào)用,存在一些性能損失,如果用戶想要減少這方面的損耗,可以選擇重載實現(xiàn)。
              public virtual void Invoke(AspectContext context, AspectDelegate next)
              {
                  InvokeAsync(context, c =>
                  {
                      next(c);
                      return Task.CompletedTask;
                  }).ConfigureAwait(false)
                              .GetAwaiter()
                              .GetResult();
              }
          
              // 默認(rèn)只需要實現(xiàn)異步攔截器方法
              public abstract Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
          }
          

          一個攔截器實現(xiàn)舉例:

          public class AddTenInterceptorAttribute : AbstractInterceptorAttribute
          {
              public override void Invoke(AspectContext context, AspectDelegate next)
              {
                  next(context);
                  AddTen(context);
              }
          
              private static void AddTen(AspectContext context)
              {
                  if (context.ReturnValue is int i)
                  {
                      context.ReturnValue = i + 10;
                  }
                  else if(context.ReturnValue is double d)
                  {
                      context.ReturnValue = d + 10.0;
                  }
              }
          
              public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
              {
                  await next(context);
                  AddTen(context);
              }
          }
          

          InterceptorAttribute攔截器使用方式

          • interface / class / method 可以設(shè)置 Attribute,如
          [AddTenInterceptor]
          public interface IGenericTest<T, R> : IDisposable
          {
              // or
              //[AddTenInterceptor]
              T GetT();
          }
          
          • 全局?jǐn)r截器中也可以設(shè)置
          public void ConfigureServices(IServiceCollection services)
          {
              services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddTenInterceptorAttribute()));
          }
          

          AbstractInterceptor

          和 AbstractInterceptorAttribute 幾乎一模一樣,不過不是Attribute,不能用于對應(yīng)場景,只能在全局?jǐn)r截器中使用。其實本身就是提供給用戶用于不想Attribute場景簡化Interceptor創(chuàng)建。

          Interceptor攔截器使用方式

          只能在全局?jǐn)r截器中設(shè)置

          public void ConfigureServices(IServiceCollection services)
          {
              services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddSixInterceptor()));
          }
          

          全局?jǐn)r截器 vs 顯示攔截器

          • 全局?jǐn)r截器,是針對所有可以代理的方法都會做攔截,只需一次聲明,全局有效
          public void ConfigureServices(IServiceCollection services)
          {
              services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddSixInterceptor()));
          }
          
          • 顯示攔截器必須使用AbstractInterceptorAttribute在所有需要的地方都顯示聲明
          [AddTenInterceptor]
          public interface IGenericTest<T, R> : IDisposable
          {
              // or
              //[AddTenInterceptor]
              T GetT();
          }
          

          所以用戶覺得怎么樣方便就怎么用就好了

          攔截器的過濾方式

          Norns.Urd 提供如下三種過濾方式

          • 全局過濾
          services.ConfigureAop(i => i.NonPredicates.AddNamespace("Norns")
              .AddNamespace("Norns.*")
              .AddNamespace("System")
              .AddNamespace("System.*")
              .AddNamespace("Microsoft.*")
              .AddNamespace("Microsoft.Owin.*")
              .AddMethod("Microsoft.*", "*"));
          
          • 顯示過濾
          [NonAspect]
          public interface IGenericTest<T, R> : IDisposable
          {
          }
          
          • 攔截器本身的過濾
          public class ParameterInjectInterceptor : AbstractInterceptor
          {
              public override bool CanAspect(MethodInfo method)
              {
                  return method.GetReflector().Parameters.Any(i => i.IsDefined<InjectAttribute>());
              }
          }
          

          AOP限制

          • 當(dāng) service type 為 class 時, 只有 virtual 且 子類能有訪問的 方法才能代理攔截
          • 有方法參數(shù)為 in readonly struct 的類型無法代理

          Interface和Abstract Class的默認(rèn)實現(xiàn)

          如果你向DI框架注冊沒有真正有具體實現(xiàn)的 InterfaceAbstract Class, Norns.Urd 會實現(xiàn)默認(rèn)的子類型。

          為什么提供這樣的功能呢?

          這是為聲明式編碼思想提供一些底層實現(xiàn)支持,這樣有更多的同學(xué)可以自定義自己的一些聲明式庫,簡化代碼,比如實現(xiàn)一個 聲明式HttpClient

          默認(rèn)實現(xiàn)限制

          • 不支持屬性注入
          • Norns.Urd 生成的默認(rèn)實現(xiàn)皆為返回類型的默認(rèn)值

          demo

          后面會完成一個簡單的httpclient作為示例,這里先做個簡單demo

          1. 假如要加 10 就是我們類似http調(diào)用的邏輯,我們就可以講全部的加10邏輯放在攔截器中
          public class AddTenAttribute : AbstractInterceptorAttribute
          {
              public override void Invoke(AspectContext context, AspectDelegate next)
              {
                  next(context);
                  AddTen(context);
              }
          
              private static void AddTen(AspectContext context)
              {
                  if (context.ReturnValue is int i)
                  {
                      context.ReturnValue = i + 10;
                  }
                  else if(context.ReturnValue is double d)
                  {
                      context.ReturnValue = d + 10.0;
                  }
              }
          
              public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
              {
                  await next(context);
                  AddTen(context);
              }
          }
          
          1. 定義聲明式client
          [AddTen]
          public interface IAddTest
          {
              int AddTen();
          
              // 對于接口中的默認(rèn)實現(xiàn),并不會被Norns.Urd替代,這樣可以提供某些場景用戶可以自定義實現(xiàn)邏輯
              public int NoAdd() => 3;
          }
          
          1. 注冊client
          services.AddTransient<IAddTest>();
          services.ConfigureAop();
          
          1. 使用
          [ApiController]
          [Route("[controller]")]
          public class WeatherForecastController : ControllerBase
          {
              IAddTest a;
              public WeatherForecastController(IAddTest b)
              {
                  a = b;
              }
          
              [HttpGet]
              public int GetAddTen() => a.AddTen();
          }
          

          InjectAttribute

          InjectAttribute 是對 Interface和Abstract Class的默認(rèn)實現(xiàn)的功能補(bǔ)充,

          特別是在做聲明式client之類,提供自定義設(shè)置,比如interface 默認(rèn)接口實現(xiàn)時,

          用戶可能需要從DI中獲取實例,所以這里提供兩種方式做一些補(bǔ)充。

          ParameterInject

          方法參數(shù)可以設(shè)置InjectAttribute

          • 當(dāng)參數(shù)為null時,就會從 DI 中嘗試獲取實例
          • 當(dāng)參數(shù)不為null時,不會覆蓋傳值,依然時傳參值

          示例:

          public interface IInjectTest
          {
              public ParameterInjectTest T([Inject] ParameterInjectTest t = null) => t;
          }
          

          PropertyInject

          public interface IInjectTest
          {
              [Inject]
              ParameterInjectInterceptorTest PT { get; set; }
          }
          

          FieldInject

          按照業(yè)界編碼習(xí)慣, field 不推薦沒有賦值就是使用,所以該功能會導(dǎo)致代碼檢查出現(xiàn)需要修復(fù)的問題

          public class ParameterInjectTest : IInjectTest
          {
              [Inject]
              ParameterInjectInterceptorTest ft;
          }
          

          FallbackAttribute

              public class DoFallbackTest
              {
                  [Fallback(typeof(TestFallback))] // just need set Interceptor Type
                  public virtual int Do(int i)
                  {
                      throw new FieldAccessException();
                  }
          
                  [Fallback(typeof(TestFallback))]
                  public virtual Task<int> DoAsync(int i)
                  {
                      throw new FieldAccessException();
                  }
              }
          
              public class TestFallback : AbstractInterceptor
              {
                  public override void Invoke(AspectContext context, AspectDelegate next)
                  {
                      context.ReturnValue = (int)context.Parameters[0];
                  }
          
                  public override Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
                  {
                      var t = Task.FromResult((int)context.Parameters[0]);
                      context.ReturnValue = t;
                      return t;
                  }
              }
          
          

          Polly

          Polly is .NET resilience and transient-fault-handling library.

          這里通過Norns.Urd將Polly的各種功能集成為更加方便使用的功能

          如何啟用 Norns.Urd + Polly, 只需使用EnablePolly()

          如:

          new ServiceCollection()
              .AddTransient<DoTimeoutTest>()
              .ConfigureAop(i => i.EnablePolly())
          

          TimeoutAttribute

          [Timeout(seconds: 1)]  // timeout 1 seconds, when timeout will throw TimeoutRejectedException
          double Wait(double seconds);
          
          [Timeout(timeSpan: "00:00:00.100")]  // timeout 100 milliseconds, only work on async method when no CancellationToken
          async Task<double> WaitAsync(double seconds, CancellationToken cancellationToken = default);
          
          [Timeout(timeSpan: "00:00:01")]  // timeout 1 seconds, but no work on async method when no CancellationToken
          async Task<double> NoCancellationTokenWaitAsync(double seconds);
          

          RetryAttribute

          [Retry(retryCount: 2, ExceptionType = typeof(AccessViolationException))]  // retry 2 times when if throw Exception
          void Do()
          

          CircuitBreakerAttribute

          [CircuitBreaker(exceptionsAllowedBeforeBreaking: 3, durationOfBreak: "00:00:01")]  
          //or
          [AdvancedCircuitBreaker(failureThreshold: 0.1, samplingDuration: "00:00:01", minimumThroughput: 3, durationOfBreak: "00:00:01")]
          void Do()
          

          BulkheadAttribute

          [Bulkhead(maxParallelization: 5, maxQueuingActions: 10)]
          void Do()
          

          Norns.Urd 中的一些設(shè)計

          Norns.Urd的實現(xiàn)前提

          由于Norns.Urd的實現(xiàn)基于以下兩點(diǎn)前提

          1. 將 sync 和 async 方法同時兼容且如何將實現(xiàn)選擇權(quán)完全交予用戶

            • 其實這點(diǎn)還好,工作量變成兩倍多一些就好,sync 和 async 完全拆分成兩套實現(xiàn)。
            • 提供給用戶的Interceptor接口要提供 sync 和 async 混合在一套實現(xiàn)代碼的方案,畢竟不能強(qiáng)迫用戶實現(xiàn)兩套代碼,很多場景用戶不需要為sync 和 async 的差異而實現(xiàn)兩套代碼
          2. 不包含任何內(nèi)置DI,但要整體都為支持DI而作

            • 其實如果內(nèi)置DI容器可以讓支持 generic 場景變得非常簡單,畢竟從DI容器中實例化對象時必須有明確的類型,但是呢,現(xiàn)在已經(jīng)有了那么多實現(xiàn)的庫了,我就不想為了一些場景而實現(xiàn)很多功能(我真的懶,否則這個庫也不會寫那么久了)
            • 但是DI容器確實解耦非常棒,我自己都常常因此受益而減少了很多代碼修改量,所以做一個aop庫必須要考慮基于DI容器做支持,這樣的話,di 支持的 open generic / 自定義實例化方法都要做支持,并且aop里面還得提供用戶調(diào)用DI的方法,否則還不好用了 (這樣算下來,我真的偷懶了嗎?我是不是在給自己挖坑呀?)

          如何設(shè)計解決的?

          目前方案不一定完美,暫時算解決了問題而已 (有更好方案請一定要告訴我,我迫切需要學(xué)習(xí))

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

          以前接觸一些其他aop實現(xiàn)框架,很多都需要將攔截代碼分為 方法前 / 方法后 / 有異常等等,個人覺得這樣的形式還是一定程度上影響攔截器實現(xiàn)的代碼思路,總覺得不夠順滑

          但是像 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!");
          });
          

          攔截器也應(yīng)該可以像這樣做,所以攔截器的代碼應(yīng)該可以像這樣:

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

          sync 和 async 方法如何拆分?又如何能合并在一起呢?用戶有怎么自己選擇實現(xiàn)sync 還是 async 或者兩個都都實現(xiàn)呢?

          
          public delegate Task AsyncAspectDelegate(AspectContext context);
          
          public delegate void AspectDelegate(AspectContext context);
          
          // 拆分: 
          // 由AspectDelegate 和 AsyncAspectDelegate 建立兩套完全區(qū)分 sync 和 async 的Middleware調(diào)用鏈,具體使用哪個由具體被攔截的方法本身決定
          
          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();
              }
          
          // 合并:
          // 默認(rèn)實現(xiàn)轉(zhuǎn)換方法內(nèi)容,這樣各種攔截器都可以混在一個Middleware調(diào)用鏈中
          
              public abstract Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
          
          // 用戶自主性選擇:
          // 同時提供sync 和 async 攔截器方法可以重載,用戶就可以自己選擇了
          // 所以用戶在 async 中可以調(diào)用專門的未異步優(yōu)化代碼了,也不用說在 sync 中必須 awit 會影響性能了,
          // 你認(rèn)為影響性能,你在乎就自己都重載,不在乎那就自己選
          }
          

          沒有內(nèi)置DI,如何兼容其他DI框架呢?

          DI框架都有注冊類型,我們可以通過 emit 生成代理類,替換原本的注冊,就可以做到兼容。

          當(dāng)然每種DI框架都需要定制化的實現(xiàn)一些代碼才能支持(唉,又是工作量呀)

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

          由于這種DI框架的用法,無法通過Func函數(shù)拿到實際會使用的類型,只能根據(jù)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 生成泛型類型一點(diǎn)問題都沒有,唯一的難點(diǎn)是不好生成 Get<T>() 這樣的方法調(diào)用, 因為IL需要反射找到的具體方法,比如Get<int>() Get<bool>() 等等,不能是不明確的 Get<T>()。

          要解決這個問題就只能將實際的調(diào)用延遲到運(yùn)行時調(diào)用再生成具體的調(diào)用,偽碼大致如下:

          
          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);
          }
          
          
          
          瀏覽 37
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97亚洲综合影院 | 激情性爱网站在线观看 | 天堂色影院| 一级国产黄色片 | 高清视频在线观看一区 |