1. 程式人生 > >動手造輪子:實現一個簡單的 AOP 框架

動手造輪子:實現一個簡單的 AOP 框架

# 動手造輪子:實現一個簡單的 AOP 框架 ## Intro 最近實現了一個 AOP 框架 -- FluentAspects,API 基本穩定了,寫篇文章分享一下這個 AOP 框架的設計。 ## 整體設計 ### 概覽 ![](https://img2020.cnblogs.com/blog/489462/202006/489462-20200614164708758-1563520245.png) ### IProxyTypeFactory 用來生成代理型別,預設提供了基於 Emit 動態代理的實現,基於介面設計,可以擴充套件為其他實現方式 介面定義如下: ``` csharp public interface IProxyTypeFactory { Type CreateProxyType(Type serviceType); Type CreateProxyType(Type serviceType, Type implementType); } ``` ### IProxyFactory 用來生成代理例項,預設實現是基於 `IProxyTypeFactory` 生成代理型別之後建立例項 介面定義如下: ``` csharp public interface IProxyFactory { object CreateProxy(Type serviceType, object[] arguments); object CreateProxy(Type serviceType, Type implementType, params object[] arguments); object CreateProxyWithTarget(Type serviceType, object implement, object[] arguments); } ``` ### IInvocation 執行上下文,預設實現就是方法執行的上下文,包含了代理方法資訊、被代理的方法資訊、方法引數,返回值以及用來自定義擴充套件的一個 `Properties` 屬性 ``` csharp public interface IInvocation { MethodInfo ProxyMethod { get; } object ProxyTarget { get; } MethodInfo Method { get; } object Target { get; } object[] Arguments { get; } Type[] GenericArguments { get; } object ReturnValue { get; set; } Dictionary Properties { get; } } ``` ### IInterceptor 攔截器,用來定義公用的處理邏輯,方法攔截處理方法 介面定義如下: ``` csharp public interface IInterceptor { Task Invoke(IInvocation invocation, Func next); } ``` `invocation` 是方法執行的上下文,`next` 代表後續的邏輯處理,類似於 asp.net core 裡的 `next` ,如果不想執行方面的方法不執行 `next` 邏輯即可 ### IInterceptorResolver 用來根據當前的執行上下文獲取到要執行的攔截器,預設是基於 FluentAPI 的實現,但是如果你特別想用基於 Attribute 的也是可以的,預設提供了一個 `AttributeInterceotorResovler`,你也可以自定義一個適合自己的 `InterceptorResolver` ``` csharp public interface IInterceptorResolver { IReadOnlyList ResolveInterceptors(IInvocation invocation); } ``` ### IInvocationEnricher 上面 `IInvocation` 的定義中有一個用於擴充套件的 `Properties`,這個 `enricher` 主要就是基於 `Properties` 來豐富執行上下文資訊的,比如說記錄 `TraceId` 等請求鏈路追蹤資料,構建方法執行鏈路等 ``` csharp public interface IEnricher { void Enrich(TContext context); } public interface IInvocationEnricher : IEnricher { } ``` ### AspectDelegate `AspectDelegate` 是用來將構建要執行的代理方法的方法體的,首先執行註冊的 `InvocationEnricher`,豐富上下文資訊,然後根據執行上下文獲取要執行的攔截器,構建一個執行委託,生成委託使用了之前分享過的 `PipelineBuilder` 構建中介軟體模式的攔截器,執行攔截器邏輯 ``` csharp // apply enrichers foreach (var enricher in FluentAspects.AspectOptions.Enrichers) { try { enricher.Enrich(invocation); } catch (Exception ex) { InvokeHelper.OnInvokeException?.Invoke(ex); } } // get delegate var builder = PipelineBuilder.CreateAsync(completeFunc); foreach (var interceptor in interceptors) { builder.Use(interceptor.Invoke); } return builder.Build(); ``` 更多資訊可以參考原始碼:
## 使用示例 推薦和依賴注入結合使用,主要分為以微軟的注入框架為例,有兩種使用方式,一種是手動註冊代理服務,一種是自動批量註冊代理服務,來看下面的例項就明白了 ### 手動註冊代理服務 使用方式一,手動註冊代理服務: 為了方便使用,提供了一些 `AddProxy` 的擴充套件方法: ``` csharp IServiceCollection services = new ServiceCollection(); services.AddFluentAspects(options => { // 註冊攔截器配置 options.NoInterceptProperty(f => f.Name); options.InterceptAll() .With() ; options.InterceptMethod(x => x.Name == nameof(DbContext.SaveChanges) || x.Name == nameof(DbContext.SaveChangesAsync)) .With() ; options.InterceptMethod(f => f.Fly()) .With(); options.InterceptType() .With(); // 註冊 InvocationEnricher options .WithProperty("TraceId", "121212") ; }); // 使用 Castle 生成代理 services.AddFluentAspects(options => { // 註冊攔截器配置 options.NoInterceptProperty(f => f.Name); options.InterceptAll() .With() ; options.InterceptMethod(x => x.Name == nameof(DbContext.SaveChanges) || x.Name == nameof(DbContext.SaveChangesAsync)) .With() ; options.InterceptMethod(f => f.Fly()) .With(); options.InterceptType() .With(); // 註冊 InvocationEnricher options .WithProperty("TraceId", "121212") ; }, builder => builder.UseCastle()); services.AddTransientProxy(); services.AddSingletonProxy(); services.AddDbContext(options => { options.UseInMemoryDatabase("Test"); }); services.AddScopedProxy(); var serviceProvider = services.BuildServiceProvider(); ``` ### 批量自動註冊代理服務 使用方式二,批量自動註冊代理服務: ``` csharp IServiceCollection services = new ServiceCollection(); services.AddTransient(); services.AddSingleton(); services.AddDbContext(options => { options.UseInMemoryDatabase("Test"); }); var serviceProvider = services.BuildFluentAspectsProvider(options => { options.InterceptAll() .With(output); }); // 使用 Castle 來生成代理 var serviceProvider = services.BuildFluentAspectsProvider(options => { options.InterceptAll() .With(output); }, builder => builder.UseCastle()); // 忽略名稱空間為 Microsoft/System 的服務型別 var serviceProvider = services.BuildFluentAspectsProvider(options => { options.InterceptAll() .With(output); }, builder => builder.UseCastle(), t=> t.Namespace != null && (t.Namespace.StartWith("Microsft") ||t.Namespace.StartWith("Microsft"))); ``` ## More 上面的兩種方式個人比較推薦使用第一種方式,需要攔截什麼就註冊什麼代理服務,自動註冊可能會生成很多不必要的代理服務,個人還是比較喜歡按需註冊的方式。 這個框架還不是很完善,有一些地方還是需要優化的,目前還是在我自己的類庫中,因為我的類庫裡要支援 net45,所以有一些不好的設計改起來不太方便,打算遷移出來作為一個單獨的元件,直接基於 netstandard2.0/netstandard2.1, 甩掉 netfx 的包袱。 ## Reference -
-