Dora.Interception, 為.NET Core度身打造的AOP框架:不一樣的Interceptor定義方式

来源:https://www.cnblogs.com/artech/archive/2018/01/22/dora2-02.html
-Advertisement-
Play Games

相較於社區其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的編程方式。我們並沒有為Interceptor定義一個介面,正是因為不需要實現一個預定義的介面,Dora.Interception下的Interceptor定義變得更加自由。除此之外,Interc... ...


相較於社區其他主流的AOP框架,Dora.Interception在Interceptor提供了完全不同的編程方式。我們並沒有為Interceptor定義一個介面,正是因為不需要實現一個預定義的介面,Dora.Interception下的Interceptor定義變得更加自由。除此之外,Interceptor的非同步執行是我在設計Dora.Interception之初最為關心的問題,也就是說如果Interceptor應用的目標方法是非同步的,Interceptor自身也應該被賦予非同步執行的能力。接下來我們就來聊聊如果你使用了Dora.Interception,如何定義你的Interceptor。

目錄
一、兩種代理類型生成方式
二、InterceptorDelegate
三、定義Interceptor類型
四、支持方法註入

一、兩種代理類型生成方式

Dora.Interception採用動態生成代理類型(採用IL Emit)的方式來實現針對目標方法的攔截,具體來說我們提供了兩種類型的代理類型生成方式:

  • 如果目標類型實現了某個介面,我們會讓生成的代理類型實現這個介面,在代理類型中實現的介面方法會執行攔截操作,這意味著定義在介面中的所有方法都是可以被攔截的。代理對象會封裝目標對象,如果需要最終調用目標方法,被封裝的這個目標對象相應的方法會被調用。我們將這種形式的代理類型生成方式成為“基於介面的代碼生成”。
  • 如果目標類型沒有實現介面,那麼生成的代理類型會直接派生於這個類型,如果定義在基類中的某個虛方法需要被攔截,我們會在代理類中通過重寫該方法來執行攔截操作。針對目標方法的調用可以通過調用基類對應的方法來實現。我們將這種形式的代理類型生成方式成為“基於虛方法的代碼生成”。

二、InterceptorDelegate

AOP的核心在於將一些非業務的功能定義成相應的Interceptor,並以橫切(Crosscutting)的形式註入到針對目標方法的調用過程中。換句話說,Interceptor需要攔截目標方法的執行,併在針對當前執行方法的上下文中執行被註入的操作。如果我們將方法執行的上下文定義成一個InvocationContext,那麼Interceptor需要執行的攔截操作就可以表示成一個Action<InvocationContext>。但是我們在上面說過了,一個Interceptor應該被賦予非同步執行的能力,按照基於Task的並行編程模式,Interceptor自身執行的攔截操作應該表示成一個Func<InvocationContext, Task>

我們在Dora.Interception定義瞭如下這個抽象的InvocationContext來表示需要被攔截的方法執行上下文。它的Method和TargetMethod返回代表當前方法的MethodBase對象,如果採用基於介面的代理類型生成方式,前者表示定義在介面上的方法,後者則表示定義在目標類型上的方法。如果採用基於虛方法的代理類型生成方式,兩個屬性返回的是同一個對象,表示定義在被攔截類型中的方法。至於Proxy和Target則很明顯,分別表示當前的代理對象和被封裝的目標對象,如果採用基於虛方法的代理類型生成方式,兩個屬性返回同一個對象。

  1 public abstract class InvocationContext
  2 {
  3     public abstract MethodBase Method { get; }
  4     public MethodBase TargetMethod { get; }
  5     public abstract object Proxy { get; }
  6     public abstract object Target { get; }
  7     public abstract object[] Arguments { get; }
  8     public abstract object ReturnValue { get; set; }
  9 }

InvocationContext的Arguments表示當前方法調用的參數,既包括一般的輸入參數,也包括ref/out參數。值得一提的是,Arguments屬性是可讀可寫的,也就說Interceptor可以通過修改該屬性中某個元素的值進而實現修改某個參數值的目的。由於整個調用過程共用同一個InvocationContext對象,所以某個Interceptor對Arguments所作的任何修改都將影響到後續Intercecptor以及最終目標方法的執行。InvocationContext的ReturnValue表示方法調用的返回值。如果目標方法最終被調用,它的返回值將最終反映在這個屬性上。這個屬性是可讀可寫的,任意Interceptor都可以通過修改這個屬性得到改變方法調用返回值的目的。

由於當前方法調用的執行上下文被表示成一個InvocationContext對象,所以實現在Interceptor上的攔截操作可以表示成一個Func<InvocationContext, Task>類型的委托。不過為了編程方便,我們專門定義瞭如下這個對應的委托類型InterceptDelegate。由於一個方法上可以同時應用多個Interceptor,那麼對應一個Interceptor在完成了自身定義的攔截操作之後,它還將決定是否繼續調用後續的Interceptor或者目標方法,或者說針對後續Interceptor或者目標方法的調用也屬於當前攔截操作的一部分,所以我們定義了另一個委托類型InterceptorDelegate來表示一個Interceptor對象。

  1 public delegate Task InterceptDelegate(InvocationContext context);
  2 public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);

對於表示Interceptor的InterceptorDelegate委托,它的輸入和輸出都是InterceptDelegate委托,Interceptor通過作為輸入的InterceptDelegate委托實現針對後續Interceptor或者目標方法的調用,它返回的InterceptDelegate委托對象則體現實現的攔截操作。從這個意義上講,一個InterceptorDelegate委托不僅僅表示一個單一的Interceptor對象,也可以表示由多一個Interceptor組成的Interceptor Chain。從另一個角度講,由於一個Interceptor已經實現了針對後續Interceptor的執行,所以一個Interceptor本身就表示一個Interceptor Chain

三、定義Interceptor類型

雖然Dora.Interception在底層總是使用一個InterceptorDelegate委托表示Interceptor(Chain),為了編程上的便利,我們依然將Interceptor定義成一個類型,我們定義的Interceptor類型只需要採用如下的“約定”即可:

  • Interceptor類型無需實現任何的介面,我們只需要定義一個普通的公共實例類型即可。
  • Interceptor類型必須具有一個公共構造函數,並且該構造函數的第一個參數的類型必須是InterceptDelegate,後者用於調用後續的Interceptor或者目標方法。
  • 除了第一個參數之外,上述這個構造函數可以包含任意的參數定義
  • 攔截功能實現在約定的InvokeAsync的方法中,這是一個返回類型為Task的非同步方法,它的第一個參數類型為InvocationContext。
  • 除了表示當前執行上下文的參數之外,InvokeAsync可以包含任意的參數定義,但是要求這些參數能夠以DI的方式來提供。
  • 當前Interceptor是否調用後續的Interceptor或者目標方法,取決於你是否調用構造函數傳入的這個InterceptDelegate委托對象。

接下來我們就通過實例演示的方式來簡單介紹一下如何遵循上述的這些約定來定義我們的Interceptor類型。如下麵的代碼片段所示,作為Interceptor類型的FoobarInterceptor具有一個公共的實例構造函數,作為強制要求的第一個參數next表示用於調用後續Interceptor或者目標方法的InterceptDelegate委托對象。除了該參數,我們還定義了額外兩個介面類型的參數,這些參數都被保存到對應的欄位或者屬性上。攔截操作定義在InvokeAsync方法,這個方法的方法名(InvokeAsync)、返回類型(Task)和第一個參數的類型(InvocationContext)都是我們約定的一部分。在這個方法中,我們輸出Foo和Bar屬性,並最終利用構造函數指定的InterceptDelegate委托對象將調用向後傳遞。

  1 public class FoobarInterceptor
  2 {
  3     public IFoo Foo { get; }
  4     public IBar Bar { get; }
  5     private InterceptDelegate _next;
  6     public FoobarInterceptor(InterceptDelegate next, IFoo foo, IBar bar)
  7     {
  8         _next = next;
  9         this.Foo = foo;
 10         this.Bar = bar;
 11     }
 12 
 13     public Task InvokeAsync(InvocationContext context)
 14     {
 15         Console.WriteLine(this.Foo);
 16         Console.WriteLine(this.Bar);
 17         return _next(context);
 18     }
 19 }
 20 
 21 
 22 public class FoobarAttribute : InterceptorAttribute
 23 {
 24     public override void Use(IInterceptorChainBuilder builder)
 25     {
 26         builder.Use<FoobarInterceptor>(this.Order);
 27     }
 28 }
 29 
 30 public interface IFoo { }
 31 public interface IBar { }
 32 public class Foo : IFoo { }
 33 public class Bar : IBar { }

FoobarAttribute是用於註冊FoobarInterceptor的特性,FoobarAttribute派生於InterceptorAttribute這個抽象基類,關於InterceptorAttribute以及相關Interceptor註冊的類型我們將在後續的文章中進行介紹。Dora.Interception的一個顯著的特征就是與.NET Core的DI實現了無縫集成,具體體現在Interceptor中所需的任何服務都可以直接採用DI的方式來提供,比如FoobarInterceptor的Foo和Bar屬性對應的服務實例。如下麵的代碼片段所示,我們將FoobarAttribute標註到Demo類型的虛方法Invoke上。在Main方法中,我們將IFoo、IBar和Demo對應的服務註冊添加到創建的ServiceCollection上,然後調用後者的BuildInterceptableServiceProvider方法創建一個具有“攔截”特性的ServiceProvider。如果由這個ServiceProvider提供的服務類型能夠被攔截,它會利用相應的代理類型生成機制動態地生成對應的代理類型,並最終創建出對應的代理實例。

  1 class Program
  2 {
  3     static void Main()
  4     {
  5         var demo = new ServiceCollection()
  6             .AddScoped<IFoo, Foo>()
  7             .AddScoped<IBar, Bar>()
  8             .AddScoped<Demo, Demo>()
  9             .BuildInterceptableServiceProvider()
 10             .GetRequiredService<Demo>();
 11         demo.Invoke();
 12     }
 13 }
 14 
 15 public class Demo
 16 {
 17     [Foobar]
 18     public virtual void Invoke()
 19     {
 20         Console.WriteLine("Demo.Invoke() is invoked");
 21     }
 22 }

執行上面的代碼會在控制臺上產生如下的輸出結果,我們可以看出應用在Domo.Invoke方法上的FoobarInteceptor被正常執行,它依賴的兩個服務類型Foo和Bar正好與服務註冊一致。

image

四、支持方法註入

對於面定義的FoobarInteceptor來說,它依賴的兩個服務Foo和Bar實際上是通過構造器註入的方式提供的,實際上我們還具有更加簡潔的方案,那就是直接在InvokeAsync方法中對它們進行註入,這也是我們為什麼不為Interceptor定義介面的原因。直接採用方法註入會讓你的Interceptor變得更加簡潔。

  1 public class FoobarInterceptor
  2 {
  3     private InterceptDelegate _next;
  4     public FoobarInterceptor(InterceptDelegate next)
  5     {
  6         _next = next;
  7     }
  8 
  9     public Task InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
 10     {
 11         Console.WriteLine(foo);
 12         Console.WriteLine(bar);
 13         return _next(context);
 14     }
 15 }


Dora.Interception, 為.NET Core度身打造的AOP框架 [1]:全新的版本
Dora.Interception, 為.NET Core度身打造的AOP框架 [2]:不一樣的Interceptor定義方式
Dora.Interception, 為.NET Core度身打造的AOP框架 [3]:Interceptor的註冊

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

-Advertisement-
Play Games
更多相關文章
  • 上一篇講瞭如何再控制台將RocketMQ跑起來,本篇講解,在asp.net mvc種跑起來,含(發佈、訂閱)。 本次將不挨個貼源碼,直接展示目錄,根據上一篇文章,進行相應的調整即可。 1.新建一個類庫,將MQ公共部分提出來: 如: 2.新建一個asp.net mvc 項目(需要在App_Data中修 ...
  • 代碼如下: static class Ulity { public static string[] Split(this string source,int count) { List<string> list = new List<string>(); string temp = string.E ...
  • 1.將線程設置為應用程式空閑處理 Dispatcher.Invoke(new Action(() => { 載入控制項; }), System.Windows.Threading.DispatcherPriority.ApplicationIdle); 2.提示等待框 在新線程中啟動等待框 http: ...
  • 一、背景 常見的一種資料庫設計是使用連續的整數為做主鍵,當新的數據插入到資料庫時,由資料庫自動生成。但這種設計不一定適合所有場景。 隨著越來越多的使用Nhibernate、EntityFramework等ORM(對象關係映射)框架,應用程式被設計成為工作單元(Unit Of Work)模式,需要在數 ...
  • 我們經常會遇到這樣的問題,在資料庫中的文件存放的是web格式或者是絕對路徑,以及使用的是百度上傳或者其他上傳組件,造成了很多非同步上傳的冗餘文件,如果客戶需要我們導出企業官網中的產品圖片,我們該如何處理? 很簡單,當然是自己寫個工具來讀取,然後複製文件啦! 所以這款工具就應運而生了,大大的提高開發效率 ...
  • public static string MD5Encrypt(string clearText) { string result = string.Empty; byte[] byteArray = MD5.Create().ComputeHash(Encoding.Default.GetByte ...
  • 本次主要實現列表的編輯及下拉列表的綁定 先看效果圖: 主要用DropDownList綁定下拉列後端代碼: 1:定義一個存下拉數據類 2:編輯的方法 前端代碼: ...
  • EF的CodeFirst模式自動遷移(適用於開發環境) 1、開啟EF數據遷移功能 NuGet包管理器 >程式包管理控制台 >Enable-Migrations 2、資料庫上下文設置為遷移至最後一個版本 MigrateDatabaseToLatestVersion<資料庫上下文,遷移配置文件> 3、設 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...