全新升級的AOP框架Dora.Interception[4]: 基於Lambda表達式的攔截器註冊方式

如果攔截器應用的目標類型是由自己定義的,Dora.Interception(github地址,覺得不錯不妨給一顆星)可以在其類型或成員上標註InterceptorAttribute特性來應用對應的攔截器。如果對那個的程序集是由第三方提供的呢?此時我們可以採用提供的第二種基於表達式的攔截器應用方式。這裏的攔截器是一個調用目標類型某個方法或者提取某個屬性的Lambda表達式,我們採用這種強類型的編程方式得到目標方法,並提升編程體驗。(拙著《ASP.NET Core 6框架揭祕》於日前上市,加入讀者羣享6折優惠)

目錄
一、IInterceptorRegistry
二、將攔截器應用到某個類型
三、應用到指定的方法和屬性
四、指定構建攔截器的參數
五、攔截屏蔽
六、兩個後備方法

一、IInterceptorRegistry

以表達式採用強類型的方式將指定類型的攔截器應用到目標方法上是藉助如下這個IInterceptorRegistry接口完成的。IInterceptorRegistry接口提供了一個For<TInterceptor>方法以待註冊的攔截器類型關聯,參數arguments用來提供構建攔截器對象的參數。該方法會返回一個IInterceptorRegistry<TInterceptor>對象,它提供了一系列的方法幫助我們將指定的攔截器應用到指定目標類型(通過泛型參數類型TTarget表示)相應的方法上。

public interface IInterceptorRegistry
{

    IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
    ...
}

public interface IInterceptorRegistry<TInterceptor>
{
    IInterceptorRegistry<TInterceptor> ToAllMethods<TTarget>(int order);
    IInterceptorRegistry<TInterceptor> ToMethod<TTarget>(int order, Expression<Action<TTarget>> methodCall);
    IInterceptorRegistry<TInterceptor> ToMethod(int order, Type targetType, MethodInfo method);
    IInterceptorRegistry<TInterceptor> ToGetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
    IInterceptorRegistry<TInterceptor> ToSetMethod<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
    IInterceptorRegistry<TInterceptor> ToProperty<TTarget>(int order, Expression<Func<TTarget, object?>> propertyAccessor);
}

封裝了IServiceCollection集合的InterceptionBuilder提供了一個RegisterInterceptors擴展方法,我們可以利用該方法定義的Action<IInterceptorRegistry>類型的參數來使用上述的這個IInterceptorRegistry接口。不論是IServiceCollection接口的BuildInterceptableServiceProvider擴展方法,還是IHostBuilder接口的UseInterception方法均提供了一個可選的Action<InterceptionBuilder>委託類型的參數。

public sealed class InterceptionBuilder { public IServiceCollection Services { get; }
public InterceptionBuilder(IServiceCollection services); } public static class Extensions { public static InterceptionBuilder RegisterInterceptors(this InterceptionBuilder builder, Action<IInterceptorRegistry> register); public static IServiceProvider BuildInterceptableServiceProvider(this IServiceCollection services, Action<InterceptionBuilder>? setup = null); public static IHostBuilder UseInterception(this IHostBuilder hostBuilder, Action<InterceptionBuilder>? setup = null); }

二、將攔截器應用到某個類型

類似與將InterceptorAttribute標註到某個類型上,我們也可以採用這種方式將指定的攔截器應用到目標類型上,背後的含義就是應用到該類型可以被攔截的所以方法上(含屬性方法)。

public class FoobarInterceptor
{
    public ValueTask InvokeAsync(InvocationContext invocationContext)
    {
        var method = invocationContext.MethodInfo;
        Console.WriteLine($"{method.DeclaringType!.Name}.{method.Name} is intercepted.");
        return invocationContext.ProceedAsync();
    }
}

public class Foobar
{
    public virtual void M() { }
    public virtual object? P { get; set; }
}

我們可以採用如下的方式將調用IInterceptorRegistry<TInterceptor>的ToAllMethods<TTarget>方法將上面定義的攔截器FoobarInterceptor應用到Foobar類型的所有方法上。

var foobar = new ServiceCollection()
    .AddSingleton<Foobar>()
    .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
    .GetRequiredService<Foobar>();

foobar.M();
foobar.P = null;
_ = foobar.P;

static void RegisterInterceptors(IInterceptorRegistry registry)
{
    var foobar = registry.For<FoobarInterceptor>();
    foobar.ToAllMethods<Foobar>(order: 1);
}

從如下所示的執行結果可以看出,Foobar類型的M方法和P屬性均被FoobarInterceptor攔截下來(源代碼)。

image

三、應用到指定的方法和屬性

我們可以通過指定調用方法或者獲取屬性的表達式來指定攔截器應用的目標方法。我們將目標類型Foobar定義成如下的形式,兩個重載的M方法和三個屬性均是可以攔截的。

public class Foobar
{
    public virtual void M(int x, int y) { }
    public virtual void M(double x, double y) { }
    public virtual object? P1 { get; set; }
    public virtual object? P2 { get; set; }
    public virtual object? P3 { get; set; }
}

我們利用如下的代碼將上面定義的FoobarInterceptor應用到Foobar類型相應的成員上。具體來說,我們調用ToMethod<TTarget>方法應用到兩個重載的M方法,調用ToProperty<TTarget>方法應用到P1屬性的Get和Set方法上,調用ToGetMethod<TTarget>和ToSetMethod<TTarget>方法應用到P2屬性的Get方法和P3屬性的Set方法。

var provider = new ServiceCollection()
    .AddSingleton<Foobar>()
    .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors));

var foobar = provider.GetRequiredService<Foobar>();

foobar.M(1, 1);
foobar.M(3.14, 3.14);
foobar.P1 = null;
_ = foobar.P1;
foobar.P2 = null;
_ = foobar.P2;
foobar.P3 = null;
_ = foobar.P3;
Console.ReadLine();

static void RegisterInterceptors(IInterceptorRegistry registry)
{
    var foobar = registry.For<FoobarInterceptor>();
    foobar
        .ToMethod<Foobar>(order: 1, it => it.M(default(int), default(int)))
        .ToMethod<Foobar>(order: 1, it => it.M(default(double), default(double)))
        .ToProperty<Foobar>(order: 1, it => it.P1)
        .ToGetMethod<Foobar>(order: 1, it => it.P2)
        .ToSetMethod<Foobar>(order: 1, it => it.P3)
        ;
}

程序運行後,針對Foobar相應成員的攔截體現在如下所示的輸出結果上(源代碼)。

image

四、指定構建攔截器的參數

如果應用的攔截器類型構造函數指定了參數,我們採用這種註冊方式的時候也可以指定參數。以如下這個FoobarInterceptor爲例,其構造函數中指定了兩個參數,一個是代表攔截器名稱的name參數,另一個是IFoobar對象。

public class FoobarInterceptor
{
    public FoobarInterceptor(string name, IFoobar foobar)
    {
        Name = name;
        Foobar = foobar;
    }

    public string Name { get; }
    public IFoobar Foobar { get; }
    public ValueTask InvokeAsync(InvocationContext invocationContext)
    {
        Console.WriteLine($"{invocationContext.MethodInfo.Name} is intercepted by FoobarInterceptor {Name}.");
        Console.WriteLine($"Foobar is '{Foobar.GetType()}'.");
        return invocationContext.ProceedAsync();
    }
}
public interface IFoobar { }
public class Foo : IFoobar { }
public class Bar: IFoobar { }

public class Invoker
{
    public virtual void M1() { }
    public virtual void M2() { }
}

由於字符串參數name無法從依賴注入容器提取,所以在註冊FoobarInterceptor是必須顯式指定。如果容器能夠提供IFoobar對象,但是希望指定一個不通過的對象,也可以在註冊的時候顯式指定一個IFoobar對象。我們按照如下的方式將兩個不同的FoobarInterceptor對象分別應用到Invoker類型的Invoke1和Invoke2方法上,並分別將名稱設置爲Interceptor1和Interceptor2,第二個攔截器還指定了一個Bar對象作爲參數(容器默認提供的IFoobar對象的類型爲Foo)。

var invoker = new ServiceCollection()
    .AddSingleton<Invoker>()
    .AddSingleton<IFoobar, Foo>()
    .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
    .GetRequiredService<Invoker>();

invoker.M1();
Console.WriteLine();
invoker.M2();

static void RegisterInterceptors(IInterceptorRegistry registry)
{
    registry.For<FoobarInterceptor>("Interceptor1").ToMethod<Invoker>(order: 1, it => it.M1());
    registry.For<FoobarInterceptor>("Interceptor2", new Bar()).ToMethod<Invoker>(order: 1, it => it.M2());
}

程序運行之後,兩個FoobarInterceptor對象的名稱和依賴的IFoobar對象的類型以如下的形式輸出到控制檯上(源代碼)。

image

五、攔截屏蔽

除了用來註冊指定攔截器的For<TInterceptor>方法,IInterceptorRegistry接口還定義瞭如下這些用來屏蔽攔截的SuppressXxx方法。

public interface IInterceptorRegistry
{
    IInterceptorRegistry<TInterceptor> For<TInterceptor>(params object[] arguments);
    IInterceptorRegistry SupressType<TTarget>();
    IInterceptorRegistry SupressTypes(params Type[] types);
    IInterceptorRegistry SupressMethod<TTarget>(Expression<Action<TTarget>> methodCall);
    IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
    IInterceptorRegistry SupressProperty<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
    IInterceptorRegistry SupressSetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
    IInterceptorRegistry SupressGetMethod<TTarget>(Expression<Func<TTarget, object?>> propertyAccessor);
}

我們可以採用如下的方式會將屏蔽掉Foobar類型所有成員的攔截特性,雖然攔截器FoobarInterceptor被註冊到了這個類型上(源代碼)。

var foobar = new ServiceCollection()
    .AddSingleton<Foobar>()
    .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
    .GetRequiredService<Foobar>();
...

static void RegisterInterceptors(IInterceptorRegistry registry)
{
    registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
    registry.SupressType<Foobar>();
}

下面的程序明確屏蔽掉Foobar類型如下這些方法的攔截能力:M方法,P1屬性的Get和Set方法(如果有)以及P屬性的Get方法(源代碼)。

var foobar = new ServiceCollection()
    .AddSingleton<Foobar>()
    .BuildInterceptableServiceProvider(interception => interception.RegisterInterceptors(RegisterInterceptors))
    .GetRequiredService<Foobar>();

...

static void RegisterInterceptors(IInterceptorRegistry registry)
{
    registry.For<FoobarInterceptor>().ToAllMethods<Foobar>(order: 1);
    registry.SupressMethod<Foobar>(it=>it.M());
    registry.SupressProperty<Foobar>(it => it.P1);
    registry.SupressGetMethod<Foobar>(it => it.P2);
}

六、兩個後備方法

通過指定調用目標方法或者提取屬性的表達式來提供攔截器應用的方法和需要屏蔽的方法提供了較好的編程體驗,但是能夠提供這種強類型編程模式的前提是目標方法或者屬性是公共成員。對於受保護(protected)的方法和屬性,我們只能使用如下兩個後備方法,指定代表目標方法的MethodInfo對象。

public interface IInterceptorRegistry<TInterceptor>
{
      IInterceptorRegistry<TInterceptor> ToMethods<TTarget>(int order, params MethodInfo[] methods);
}

public interface IInterceptorRegistry
{
    IInterceptorRegistry SupressMethods(params MethodInfo[] methods);
}

全新升級的AOP框架Dora.Interception[1]: 編程體驗
全新升級的AOP框架Dora.Interception[2]: 基於約定的攔截器定義方式
全新升級的AOP框架Dora.Interception[3]: 基於“特性標註”的攔截器註冊方式
全新升級的AOP框架Dora.Interception[4]: 基於“Lambda表達式”的攔截器註冊方式
全新升級的AOP框架Dora.Interception[5]: 實現任意的攔截器註冊方式
全新升級的AOP框架Dora.Interception[6]: 框架設計和實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章