Dora.Interception, 為.NET Core度身打造的AOP框架[3]:Interceptor的註冊

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

在 《不一樣的Interceptor》中我們著重介紹了Dora.Interception中最為核心的對象Interceptor,以及定義Interceptor類型的一些約定。由於Interceptor總是通過攔截某個方法的調用進而實現對前置或者後置操作的註入,所以我們定義的Interceptor類型... ...


在《不一樣的Interceptor》中我們著重介紹了Dora.Interception中最為核心的對象Interceptor,以及定義Interceptor類型的一些約定。由於Interceptor總是通過攔截某個方法的調用進而實現對前置或者後置操作的註入,所以我們定義的Interceptor類型總是需要與對應的目標方法進行映射。在預設的情況下,這種映射是通過在目標類型或者方法上標註特性的方式來實現的。對於任何一個Interceptor類型,我們總是需要為它定義一個對應的特性類型,這些特性具有一個共同的基類InterceptorAttribute。

目錄
一、InterceptorAttribute
二、如何定義和使用InterceptorAttribute
三、InterceptorAttribute的作用域
四、屏蔽某種類型的InterceptorAttribute
五、對其他註冊方式的支持

一、InterceptorAttribute

如下所示的是InterceptorAttribute特性的定義,我們可以看到它實現了一個名為IInterceptorProvider的介面,顧名思義,該介面表示為Dora.Interception的Interceptor Chain的構建系統提供單個Interceptor。昨天有人問我為什麼不將Interceptor直接定義成Attribute,那麼就可以直接標準在目標類型或其成員上了?對於這個問題我是這麼想的:作為一個攔截器,Interceptor只需要考慮如何實現其攔截操作就可以了,而對應的Attribute的職責是如何向Interceptor Chain構建系統提供對應的Interceptor,按照我們熟悉的“單一職責”的基本設計原則,兩者是應該分離的。從另一個角度來講,InterceptorAttribute僅僅體現了Interceptor的一種“註冊方式“,除了這種特性標準的註冊方式,Interceptor完全還可以採用其他的註冊方式,比如基於自定義映射規則,或者配置文件的方式。雖然在設計層面我將兩者嚴格地區分開來,但是最終用戶在定義Interceptor類型的時候是完全可以將兩者合二為一的,我們只需要將Interceptor同時定義成繼承InterceptorAttribute的特性類型就可以了。

IInterceptorProvider介面具有兩個成員,其中核心成員Use實現了針對Interceptor的註冊。至於另一個名為AllowMutiple的屬性,它表示由通過具有相同類型的InterceptorProvider提供的Interceptor是否可以同時應用到同一個方法上

  1 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method| AttributeTargets.Property, AllowMultiple = false)]
  2 public abstract class InterceptorAttribute : Attribute, IInterceptorProvider
  3 {
  4    public int Order { get; set; }
  5    public bool AllowMultiple {get;}
  6    public abstract void Use(IInterceptorChainBuilder builder);
  7 }
  8 
  9 public interface IInterceptorProvider
 10 {
 11     void Use(IInterceptorChainBuilder builder);
 12     bool AllowMultiple { get; }
 13 }

IInterceptorProvider介面的Use方法具有一個類型為IInterceptorChainBuilder的參數,該介面表示一個用於構建Interceptor Chain的InterceptorChainBuilder對象,我們可以調用其Use方法向它提供一個Interceptor對象。該方法的第一個參數表示提供的Interceptor對象,而第二個參數(order)則表示這個Interceptor在最終構建的Interceptor Chain中所處的位置。除了這個Use方法,我們還額外定義了兩個同名的擴展方法,其中Use<T>是我們最為常用的。

  1 public interface IInterceptorChainBuilder
  2 {   
  4     IInterceptorChainBuilder Use(InterceptorDelegate interceptor, int order);
  5     ...
  7 }
  8 
  9 public static class InterceptorChainBuilderExtensions
 10 {
 11     public static IInterceptorChainBuilder Use<TInterceptor>(this IInterceptorChainBuilder builder, int order, params object[] arguments);
 12     public static IInterceptorChainBuilder Use(this IInterceptorChainBuilder builder, Type interceptorType, int order, params object[] arguments);
 13 }

我們現在在回頭看看InterceptorAttribute類型,這個類型具有一個Order屬性正好對應於上面Use方法的order參數,而實現的AllowMultiple方法在預設的情況下與AttributeUsage.AllowMultiple屬性具有相同的值。所有派生於InterceptorAttribute的子類都需要重寫用於提供對應Interceptor的Use方法,一般來說我們只需要調用作為參數的IInterceptorChainBuilder對應的Use方法即可。在標註具有某個InterceptorAttribute的時候,我們可以按照如下的方式通過Order屬性控制Interceptor的執行順序:

  1 public class Foobar
  2 {
  3    [Foo(Order = 1)]
  4    [Bar(Order = 2)]
  5     public virtual void Invoke()
  6    {}
  7 }

二、如何定義InterceptorAttribute

當我們為某個Interceptor類型定義對應InterceptorProvider特性的時候,只需要繼承InterceptorAttribute類型,並實現其Use方法即既可以,但是在調用IInterceptorChainBuilder的Use方法的時候針對參數的指定則取決於Interceptor類型構造函數的定義,以及針對DI的服務註冊。舉個簡單的例子,如下這個FoobarInterceptor的構造函數具有四個參數,除了第一個必需的參數由Interceptor的激活系統自行提供之外,其它的三個參數要麼通過DI系統的ServiceProvider來提供,要麼有對應的InterceptorProvdier來提供。

  1 public class FoobarInterceptor
  2 {
  3     public FoobarInterceptor(InterceptDelegate next, IFoo foo, IBar bar, string baz);
  4     public Task InvokeAsync(InvocationContext context);
  5 }

假設前面兩個參數foo和bar由DI系統的ServiceProvider來提供,當我們為InterceptorProvider定義InterceptorAttribute的時候,實現的Use方法只需要提供baz參數的值就可以了,那麼我們可以採用如下的方式來定義這個InterceptorAttribute。

  1 [AttributeUsage( AttributeTargets.Method| AttributeTargets.Class| AttributeTargets.Parameter, AllowMultiple = false )]
  2 public class FoobarAttribute : InterceptorAttribute
  3 {
  4     public string Baz { get; }
  5 
  6     public FoobarAttribute(string baz)
  7     {
  8         this.Baz = baz;
  9     }
 10     public override void Use(IInterceptorChainBuilder builder)
 11     {
 12         builder.Use<FoobarInterceptor>(this.Order, this.Baz);
 13     }
 14 }

假設我們不希望以DI的方式來提供foo和bar兩個參數,我們可以按照如下的方式在調用IInterceptorChainBuilder的Use方法是顯式提供這兩個參數的值。

  1 [AttributeUsage( AttributeTargets.Method| AttributeTargets.Class| AttributeTargets.Property, AllowMultiple = false )]
  2 public class FoobarAttribute : InterceptorAttribute
  3 {
  4     public string Baz { get; }
  5 
  6     public FoobarAttribute(string baz)
  7     {
  8         this.Baz = baz;
  9     }
 10     public override void Use(IInterceptorChainBuilder builder)
 11     {
 12         builder.Use<FoobarInterceptor>(this.Order, this.Baz, new Bar(), new Foo());
 13     }
 14 }

三、InterceptorAttribute的作用域

Dora.Interception支持標註在類型、方法和屬性上的InterceptorAttribute。對於應用在類型上的InterceptorAttribute特性,由它提供的Interceptor實際上是應用到該類型上的所有可以被攔截的方法上。如果InterceptorAttribute被標註到屬性成員上,意味著該屬性的Get和Set方法同時應用了對應的Interceptor。如果我們只需要將Interceptor應用到某個屬性的Get方法或者Set方法上,我們可以選擇將 對應的InterceptorAttribute單獨應用到Get或者Set方法上。除此之外Dora.Interception還支持繼承的InterceptorAttribute,也就是說標註到基類上的InterceptorAttribute會自動被子類繼承。

在解析InterceptorAttribute特性的時候,我特意屏蔽了應用在介面上的特性,我是這樣考慮的:介面是一個多方契約,它不應該考慮實現的細節,而基於AOP的攔截則屬於單方的實現行為,所以InterceptorAttribute是不應該標註在介面上。我知道很多AOP框架(比如Unity)是可以直接將Interceptor(CallHandler)應用在介面上的,但是我覺得這一點不妥。

值得一提的是InterceptorAttribute的AllowMultiple屬性,如果該屬性返回True,意味針對這個類型的所有特性標註都是有效的。如果我們希望某個InterceptorAttribute提供的Interceptor在最終的目標方法上只能執行一次,我們需要通過應用AttributeUsage特性並將其AllowMultiple設置為False。我們知道AttributeUsage的AllowMultiple屬性只能控制對應的特性在同一個目標成員上的標註次數,也就是說對於一個AllowMultiple設置為False的Attribute,我們可以同時將其標註到類型和它的成員上的。Dora.Interception對此作了相應的處理,確保只有更接近目標方法上的特性採用有效的

以如下的定義為例,如果FoobarAttribute的AllowMultiple被設置為False,對應方法Foo,只有應用在它自身方法上的FoobarAttribute有效。對於Bar屬性的Get和Set方法,只有應用在其屬性上的FoobarAttribute有效。對於Baz屬性,應用在自身屬性上的FoobarAttribute只會應用到其Set方法上,至於其Get方法則會使用應用在自身方法上的FoobarAttribute。

  1 [Foobar]
  2 public class Demo
  3 {
  4     [Foobar]
  5     public virtual void Foo()
  6     {
  7         Console.WriteLine("Demo.Invoke() is invoked");
  8     }
  9 
 10     [Foobar]
 11     public virtual Bar Bar {get;set;}
 12 
 13     [Foobar]
 14     public virtual Baz Baz { [Foobar]get; set; }
 15 }

四、屏蔽某種類型的InterceptorAttribute

如果某種類型的InterceptorAttribute提供的Interceptor只適用於某個類型的大部分成員,我們可以選擇單獨將它標註到這些成員上。我們也可以採用排除,直接將該InterceptorAttribute標準到類型上,然後再不適用的類型成員上標註一個具有如下定義的NonInterceptableAttribute特性。當我們在使用NonInterceptableAttribute特性的時候,如果沒有指定任何參數,意味著目標類型或者類型成員(方法或者屬性)是不需要被攔截的。如果指定了IntercecptorProvider的類型,它只會屏蔽對應的IntercecptorProvider類型。

  1 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method| AttributeTargets.Property)]
  2 public class NonInterceptableAttribute : Attribute
  3 {
  4     public Type[] InterceptorProviderTypes { get; }
  5     public NonInterceptableAttribute(params Type[] interceptorProviderTypes)
  6 }

以如下的代碼片段為例,基類Foo上標註了兩個InterceptorAttribute(Interceptor1Attribute和Interceptor2Attribute),由於其子類Bar上標註了NonInterceptableAttribute,所以整個類型都是不需要被攔截的。至於另一個子類 Baz,它會繼承者兩個InterceptorAttribute,但是Invoke放上通過標註NonInterceptableAttribute屏蔽了Interceptor1Attribute,所以只有Interceptor2Attribute對它來說是有效的。

  1 [Interceptor1]
  2 [Interceptor2]
  3 public class Foo
  4 {
  5     ...
  6 }
  7 
  8 [NonInterceptable]
  9 public class Bar : Foo
 10 {
 11     [Interceptor1]
 12     public virtual void Invoke();
 13 }
 14 
 15 public class Baz : Foo
 16 {
 17     [NonInterceptable(typeof(Interceptor1Attribute))]
 18     public virtual void Invoke();
 19 }

五、對其他註冊方式的支持

我在設計Dora.Interception的時候參考了很多主流的AOP框架,而我是Unity多年深度使用者,曾經多次研究過Unity.Interception的源代碼。我覺得很多的AOP框架都過於複雜,刻意地添加了一些我覺得不它適合的特性,所以我的Dora.Interception在很多方面實際上在做減法。在Interceptor的註冊方面,實際上在開發的時候是提供了基於MatchingRule的註冊方式(這也是參考了Unity.Interception),利用定義的各種MatchingRule,我們可以採用各種匹配模式(比如類型/方法/屬性名稱、命名空間、程式集名稱以及標註的Tag)將某種的Interceptor應用到滿足規則的類型或者方法上。但是根據我個人的使用經驗來看,由於這種匹配模式過於“模糊”,我們非常容易無疑之間就擴大了某種Interceptor的應用範圍。所以我們在發佈Dora.Interception的時候將這種註冊方式移除了,所以目前為止只支持特性標註這一種註冊方式。不過Dora.Interception在這個方面給出了擴展點,如果需要可以自行實現。也許在下一個版本,我會提供一些額外的註冊方式,但是一定會要求這樣的註冊方式是“精準的”和“明確的”。

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


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

-Advertisement-
Play Games
更多相關文章
  • (贈新手,老鳥繞行0.0) Python版本:3.5.2 源碼如下: ~~~~ __Author__ = "Lance " coding = utf 8 導入相應模塊 from pygame import mixer from pynput import keyboard from pynput.k ...
  • 1.裝飾器: 本質是函數,(裝飾其他函數)就是為其他函數添加附加功能 原則: 1)不能修改被裝飾的函數的源代碼 2)不能修改被裝飾的函數的調用方式 2.實現裝飾器知識儲備: 1)函數即“變數” 2)高階函數 a.把一個函數當作實參傳給另一個函數(可以做到不修改被裝飾函數的源代碼的情況下為其添加功能) ...
  • 首先獲取兩鐘不同玉米的產量數據。 因為用了python的pandas包,因此讀取數據前需要先引入pandas包。 圖1 不同玉米用A B 表示,以下是讀取了數據的前5行。 圖2 分別把品種 A和品種B 的 玉米產量賦值給X和Y 。 然後用scipy.stats 包中的 levene 函數 檢驗數據方 ...
  • 操作系統 : CentOS7.3.1611_x64 go 版本 : go1.8.3 linux/amd64 vim版本 :version 7.4.160 vim配置go語言語法高亮的問題已經遇到過好幾次了,每次都去查找太麻煩,這裡總結下。 安裝git: 安裝vim-go : 配置vimrc文件: 如 ...
  • 前言 redis 大家都使用過, 可以安裝在windows下, 也可以安裝在linux下, 一般還是linux下安裝比較多. 這裡來介紹一下redis在linux下的安裝 一. 下載 https://redis.io/ 二. 安裝 1. 下載之後, 將文件拷貝到 linux 中. 我這裡是放在 /h ...
  • ###反射## getattr,hasattr,setattr,delattr,和類裡面的欄位有關,具體看例子 #1 class Person: def __init__(self,name,age): self.name = name self.age = age def show_lover(s ...
  • java.util.Date 就是在除了SQL語句的情況下麵使用 java.sql.Date 是針對SQL語句使用的,它只包含日期而沒有時間部分 直接說就是:java.sql.Date就是與資料庫Date相對應的一個類型,而java.util.Date是純java的Date 它都有getTime方法 ...
  • 1:列表: Python的列表比C語言的數組強大的多,數組只能存放相同類型的數據,而列表則像一個大集裝箱可以存放整形、浮點型、字元串、對象等 2:創建列表的方法 3:向列表中添加元素的方法 1)append() 向列表末尾添加一個參數 2)extend() 參數為一個列表,從原列表擴展原有列表 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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...