Dora.Interception: 一個為.NET Core度身定製的AOP框架

来源:http://www.cnblogs.com/zhushengliang/archive/2017/07/16/7192475.html
-Advertisement-
Play Games

多年從事框架設計開發使我有了一種強迫症,那就是見不得一個應用里頻繁地出現重覆的代碼。之前經常Review別人的代碼,一看到這樣的程式,我就會想如何將這些重覆的代碼寫在一個地方,然後採用“註入”的方式將它們放到需要的程式中。我們知道AOP是解決這類問題最理想的方案。為此,我自己寫了一個AOP框架,該框 ...


多年從事框架設計開發使我有了一種強迫症,那就是見不得一個應用里頻繁地出現重覆的代碼。之前經常Review別人的代碼,一看到這樣的程式,我就會想如何將這些重覆的代碼寫在一個地方,然後採用“註入”的方式將它們放到需要的程式中。我們知道AOP是解決這類問題最理想的方案。為此,我自己寫了一個AOP框架,該框架被命名為Dora.Interception。Dora.Interception已經在GitHub上開源,如果有興趣的朋友想下載源代碼或者閱讀相關文檔,可以訪問GitHub地址:https://github.com/jiangjinnan/Dora

目錄
一、Dora, 為什麼叫這個名字?
二、Dora.Interception的設計目標
三、以怎樣的方式使用Dora.Interception
四、如何定義一個Interceptor
五、定義InterceptorAttribute
六、應用InterceptorAttribute
七、以Dependency Injection的形式提供Proxy

一、Dora, 為什麼叫這個名字?

其實我最早的想法是創建一個IoC框架,並將它命名為Doraemon(哆啦A夢),因為我覺得一個理想的IoC Container就像是機器貓的二次元口袋一樣能夠提供給你期望的一切服務對象。後來覺得這名字太長,所以改名為Dora。雖然Dora這個名字聽上去有點“娘”,並且失去了原本的意思,但是我很喜歡這個單詞的一種釋義——“上帝的禮物”之一。在接觸了.NET Core的時候,我最先研究的就是它基於ServiceCollection和ServiceProvider的Dependency Injection框架,雖然這個框架比較輕量級,但是能夠滿足絕大部分項目的需求,所以我放棄了初衷。不過我依然保留了Dora這個開源項目名,併為此購買了一個功能變數名稱(doranet.org),我希望將我多年的一些想法以一系列開源框架的形式實現出來,Dora.Interception就是Dora項目的第一個基於AOP的框架。

二、Dora.Interception的設計目標

我當初在設計Dora.Interception框架時給自己確定的幾個目標:

  • Dora.Interception一個基於運行時(Run Time),而不是針對編譯時(Compile Time)的AOP框架。它通過在運行時動態創建代理對象(Proxy)來封裝目標對象(Target),並自動註入應用的攔截器(Interceptor),而不是在編譯時幫助你生成一個Proxy類型。
  • Dora.Interception需要採用一種優雅的方式來定義和應用Interceptor。
  • 能夠與.NET Core的Dependency Injection框架無縫集成
  • 能夠整合其他AOP框架。實際上Dora.Interception並沒有自行實現最底層的“攔截”機制,我使用的是Castle的DynamicProxy。如果有其他的選擇,我們可以很容易地將它引入進來。

三、以怎樣的方式使用Dora.Interception

Dora.Interception目前的版本為1.1.0,由如下兩個NuGet包來承載,由於Dora.Interception.Castle依賴於Dora.Interception,所以安裝後者即可。

  • Dora.Interception: 提供基本的API
  • Dora.Interception.Castle: 提供基於Castle(DynamicProxy)的攔截實現

四、如何定義一個Interceptor

接下來我們通過一個簡單的實例來說明一下如何採用“優雅”的方式來定義一個Interceptor類型。我們即將定義的這個CacheInterceptor可以應用到某個具有返回值的方法上實現針對返回值的緩存。如果應用了這個Interceptor,它根據傳入的參數對返回的值實施緩存。如果後續調用傳入了相同的參數,並且之前的緩存尚未過期,緩存的結果將直接作為方法的返回值,從而避免了針對目標方法的重覆調用。針對的緩存功能實現在如下這個CacheInterceptor類型中,可以看出針對的緩存是利用MemoryCache來完成的。

   1: public class CacheInterceptor
   2: {
   3:     private readonly InterceptDelegate _next;
   4:     private readonly IMemoryCache _cache;
   5:     private readonly MemoryCacheEntryOptions _options;
   6:  
   7:     public CacheInterceptor(InterceptDelegate next, IMemoryCache cache, IOptions<MemoryCacheEntryOptions> optionsAccessor)
   8:     {
   9:         _next = next;
  10:         _cache = cache;
  11:         _options = optionsAccessor.Value;
  12:     }
  13:  
  14:     public async Task InvokeAsync(InvocationContext context)
  15:     {
  21:         var key = new Cachekey(context.Method, context.Arguments);
  22:         if (_cache.TryGetValue(key, out object value))
  23:         {
  24:             context.ReturnValue = value;
  25:         }
  26:         else
  27:         {
  28:             await _next(context);
  29:             _cache.Set(key, context.ReturnValue, _options);
  30:         }

31:     }
32: public class CacheKey {...}

  33: }

CacheInterceptor體現了一個典型的Interceptor的定義方式:

  • Interceptor類型無需實現任何的介面,我們只需要定義一個普通的公共實例類型即可。
  • Interceptor類型必須具有一個公共構造函數,並且該構造函數的第一個參數的類型必須是InterceptDelegate,後者代表的委托對象會幫助我們調用後一個Interceptor或者目標方法(如果當前Interceptor已經是最後一個了)。
  • 上述這個構造函數可以包含任意的參數(比如CacheInterceptor構造函數中的cache和optionsAccessor)。這些參數可以直接利用.NET Core的Dependency Injection的方式進行註冊,對於沒有註冊的參數需要在應用該Interceptor的時候顯式提供。
  • 攔截功能實現在約定的InvokeAsync的方法中,這是一個返回類型為Task的非同步方法,它的第一個參數類型為InvocationContext,代表當前方法調用的上下文。我們可以利用這個上下文對象得到Proxy對象和目標對象,代表當前調用方法的MethodInfo對象,以及傳入的輸入參數等。除此之外,我們也可以利用這個上下文直接設置方法的返回值或者輸出參數。
  • 這個InvokeAsync方法可以包含任意後續參數,但是要求這些參數預先以Dependency Injection的形式進行註冊。這也是我沒有定義一個介面來表示Interceptor的原因,因為這樣就不能將依賴的服務直接註入到InvokeAsync方法中了。
  • 當前Interceptor是否調用後續的Interceptor或者目標方法,取決於你是否調用構造函數傳入的這個InterceptDelegate委托對象。

由於依賴的服務對象(比如CacheInterceptor依賴IMemoryCache 和IOptions<MemoryCacheEntryOptions>對象)可以直接註入到InvokeAsync方法中,所以上述這個CacheInterceptor也可以定義成如下的形式

   1: public class CacheInterceptor
   2: {
   3:     private readonly InterceptDelegate _next;
   4:     public CacheInterceptor(InterceptDelegate next)
   5:     {
   6:         _next = next;
   7:     }
   8:  
   9:     public async Task InvokeAsync(InvocationContext context, IMemoryCache cache, IOptions<MemoryCacheEntryOptions> optionsAccessor)
  10:     {
  11:         if (!context.Method.GetParameters().All(it => it.IsIn))
  12:         {
  13:             await _next(context);
  14:         }
  15:  
  16:         var key = new Cachekey(context.Method, context.Arguments);
  17:         if (cache.TryGetValue(key, out object value))
  18:         {
  19:             context.ReturnValue = value;
  20:         }
  21:         else
  22:         {
  23:             await _next(context);
  24:             _cache.Set(key, context.ReturnValue, optionsAccessor.Value);
  25:         }
  26:     }
  27: }

五、定義InterceptorAttribute

我們採用Attribute的形式來將對應的Intercepor應用到某個類型或者方法上,每個具體的Interceptor類型都具有對應的Attribute。這樣的Attribute直接繼承基類InterceptorAttribute。如下這個CacheReturnValueAttribute就是上面這個CacheInterceptor對應的InterceptorAttribute。

   1: [AttributeUsage(AttributeTargets.Method)]
   2: public class CacheReturnValueAttribute : InterceptorAttribute
   3: {
   4:     public override void Use(IInterceptorChainBuilder builder)
   5:     {
   6:         builder.Use<CacheInterceptor>(this.Order);
   7:     }
   8: }

具體的InterceptorAttribute只需要重寫Use方法將對應的Interceptor添加到Interceptor管道之中,這個功能可以直接調用作為參數的InterceptorChainBuilder對象的泛型方法Use<TInterceptor>來實現。對於這個泛型方法來說,泛型參數類型代表目標Interceptor的類型,而第一個參數表示註冊的Interceptor在整個管道中的位置。如果創建目標Interceptor而調用的構造函數的參數尚未採用Dependency Injection的形式註冊,我們需要在這個方法中提供。對於CacheInterceptor依賴的兩個對象(IMemoryCache 和IOptions<MemoryCacheEntryOptions>)都可以採用Dependency Injection的形式註入,所以我們在調用Use<CacheInterceptor>方法是並不需要提供這個兩個參數。

假設我們定義一個ExceptionHandlingInterceptor來實施自動化異常處理,當我們在創建這個Interceptor的時候需要提供註冊的異常處理類型的名稱,那麼我們需要採用如下的形式來定義對應的這個IntercecptorAttribute。如下麵的代碼片段所示,我們在調用Use<ExceptionHandlingInterceptor>方法的時候就需要顯式指定這個策略名稱。

   1: [AttributeUsage(AttributeTargets.Method|AttributeTargets.)]
   2: public class HandleExceptionAttribute : InterceptorAttribute
   3: {
   4:     public string ExceptionPolicy {get;}
   5:     public string HandleExceptionAttribute(string exceptionPolicy)
   6:     {
   7:         this.ExceptionPolicy = exceptionPolicy;
   8:     }
   9:     public override void Use(IInterceptorChainBuilder builder)
  10:     {
  11:         builder.Use<ExceptionHandlingInterceptor>(this.Order,this.ExceptionPolicy);
  12:     }
  13: }
 有的時候,IntercecptorAttribute在註冊對應Interceptor的時候需要使用到應用到當前方法或者類型上的其他Attribute。舉個簡單的例子,上述的這個HandleExceptionAttribute實際上是自動提供異常處理策略名稱,假設異常處理系統自身使用另外一個獨立的ExceptionPolicyAttribute採用如下的形式來提供這個策略。
   1: public class Foobar
   2: {
   3:    [ExceptionPolicy("DefaultPolicy")
   4:    public void Invoke()
   5:    {
   6:       ...
   7:    }
   8: }
 這個問題很好解決,因為InterceptorAttribute自身提供了應用到目標方法或者類型上的所有Attribute,所以上述這個HandleExceptionAttribute可以採用如下的定義方式。
   1: [AttributeUsage(AttributeTargets.Method)]
   2: public class HandleExceptionAttribute : InterceptorAttribute
   3: {
   4:     public override void Use(IInterceptorChainBuilder builder)
   5:     {
   6:         ExceptionPolicyAttribute  attribute = this.Attributes.ofType<ExceptionPolicyAttribute>().First();
   7:         builder.Use<Exception>(this.Order, attribute.ExceptionPolicy);
   8:     }
   9: }

六、應用InterceptorAttribute

Interceptor通過對應的InterceptorAttribute被應用到某個方法或者類型上,我們在應用InterceptorAttribute可以利用其Order屬性確定Interceptor的排列(執行)順序。如下麵的代碼片段所示, HandleExceptionAttribute和CacheReturnValueAttribute分別被應用到Foobar類型和Invoke方法上,我要求ExceptionHandlingInterceptor能夠處理CacheInterceptor拋出的異常, 那麼前者必須由於後者執行,所以我通過Order屬性控制了它們的執行順序。值得一提的是,目前我們支持兩個攔截機制,一種是基於介面,另一種是基於虛方法。如果採用基於介面的攔截機制,我要求InterceptorAttribute應用在實現類型或者其方法上,應用在介面和其方法上的InterceptorAttribute將無效。

   1: [HandleException("defaultPolicy", Order = 1)]
   2: public class Foobar: IFoobar
   3: {
   4:     [CacheReturnValue(this.Order = 2)]
   5:     public Data LoadData()
   6:     {
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #Centos visudo運行普通用戶$(whomai)執行sudo操作 http://www.cnblogs.com/xianyunhe/archive/2011/08/08/2124342.html 在/etc/gdm/custom.conf文件中添加以下內容 [daemon] Automat ...
  • 標簽:MSSQL/SQLServer/域控制器提升的先決條件驗證失敗/密碼不符合要求 概述 在安裝WindowsServer2012域控出現administrator賬戶密碼不符合要求的錯誤,但是實際該賬戶存在密碼且密碼複雜也符合要求!!! 一、問題描述 二、處理方法 運行如下命令後,重新運行檢查。 ...
  • 查看當前目錄: pwd 查看文件具體大小: ls -l 返回上一級: cd.. 返回根目錄: cd / 創建一個隱藏文件: vim .test 顯示隱藏文件: ls -a 編輯文件: 1.vim 文件名 2.按i進入插入模式 3.寫完文件之後按esc,再按shift+:鍵,再輸入wq,回車,文件就保 ...
  • 安裝office,直接引用COM控制項 C#4提供對PIA引用的一種方式:鏈接(編譯器只會將PIA中需要的部分直接嵌入到程式集中),變體(variant)被視為動態類型,以減少強制轉換需要的開銷; 不安裝office,拷貝相關dll到運行目錄,直接引用; 拷貝的dll版本最好是12及以上,12以下有兼 ...
  • 背水一戰 Windows 10 之 控制項(媒體類): Image, MediaElement ...
  • 本篇將介紹 ASP.NET Core MVC 中的過濾器的基本知識以及如何工作的。 ...
  • 忽然一想好久不寫博客了,工作原因個人原因,這些天一直希望一天假如36個小時該多好,但是,假如不可能。 由於近期在項目中接觸了lucene,這個已經沒有人維護的全文搜索框架,確實踩了不少坑,為什麼用lucene呢?其實我也不知道 關於lucene原理和全文搜索引擎的一些介紹,園子里有這幾篇寫的還是很好 ...
  • 一、前言         至今為止編程開發已經11個年頭,從 VB6.0,ASP時代到ASP.NET再到MVC, 從中見證了.NET技術發展,從無畏無知的懵懂少年,到現在的中年大叔,從中的酸甜苦辣也只有本人自知。隨著歲月的成長,技 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...