30

AOP框架Dora.Interception 3.0 [3]: 拦截器设计

 4 years ago
source link: https://www.tuicool.com/articles/IZ7vQ3j
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

对于所有的AOP框架来说,多个拦截器最终会应用到某个方法上。这些拦截器按照指定的顺序构成一个管道,管道的另一端就是针对目标方法的调用。从设计角度来将,拦截器和中间件本质是一样的,那么我们可以按照类似的模式来设计拦截器。

一、InvocationContext

我们为整个拦截器管道定义了一个统一的执行上下文,并将其命名为InvocationContext。如下面的代码片段所示,我们可以利用InvocationContext对象得到方法调用上下文的相关信息,其中包括两个方法(定义在接口和实现类型),目标对象、参数列表(含输入和输出参数)、返回值(可读写)。Properties 属性提供了一个自定义的属性容器,我们可以利用它来存放任意与当前方法调用上下文相关的信息。如果需要调用后续的拦截器或者目标方法(如果当前为最后一个拦截器),我们只需要直接调用 ProceedAsync 方法即可。

public abstract class InvocationContext
{    
    public abstract MethodInfo Method { get; }
    public MethodInfo TargetMethod { get; }
    public abstract object Target { get; }
    public abstract object[] Arguments { get; }
    public abstract object ReturnValue { get; set; }  
    public abstract IDictionary<string, object> Properties { get; }
    public Task ProceedAsync();
}

二、两个委托对象

既然所有的拦截器都是在同一个InvocationContext上下文中执行的,那么我们可以将任意的拦截操作定义成一个 Func<InvocationContext, Task> 对象。Func<InvocationContext, Task>对象不仅可以表示某项单一的拦截操作,实际上包括目标方法调用在内的整个拦截器管道都可以表示成一个Func<InvocationContext, Task>对象。由于这个委托的重要性,我们将它定义成如下这个InterceptDelegate类型。

public delegate Task InterceptDelegate(InvocationContext context);

如果以ASP.NET Core框架的请求处理管道作为类比,那么InvocationContext相当于HttpContext,而InterceptDelegate自然对应的就是RequestDelegate。我们知道ASP.NET Core框架将中间件表示成Func<RequestDelegate, RequestDelegate>对象,那么拦截器自然就可以表示成一个 Func<InterceptDelegate, InterceptDelegate> 。如果读者朋友对此不太理解,可以参阅我的文章《 200行代码,7个对象——让你了解ASP.NET Core框架的本质 》。由于拦截器的重要性,我们也将它定义成如下这个单独的InterceptorDelegate类型。

public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);

三、基于约定的拦截器定义

Dora.Interception和ASP.NET Core采用几乎一致的设计。对于ASP.NET Core来说,虽然中间件最终是通过Func<InterceptDelegate, InterceptDelegate>表示的,但是我们可以将中间件定义成一个按照约定定义的类型。Dora.Interception同样支持基于约定的拦截器类型定义。

public class FoobarInterceptor
{
    private readonly IFoo _foo;
    private readonly IBar _bar;
    private readonly string _baz;

    public FoobarInterceptor(IFoo foo, IBar bar, string baz)
    {
        _foo = foo;
        _bar = bar;
        _baz = baz;
    }

    public async InvokeAsync(InvocationContext context)
    {
        await PreInvokeAsync();
        await context.ProceedAsync();
        await PostInvokeAsync();
    }
}

如上定义的FoobarInterceptor展现了一个典型的基于约定定义的拦截器类型,它体现了如下的约定:

  • 拦截器类型是一个实例类型(不能定义成静态类型);
  • 必须具有一个公共构造函数,其中可以定义任意参数。
  • 拦截操作定义在一个名为InvokeAsync的方法中,该方法的返回类型为Task,其中包含一个 InvocationContext 类型的参数。如果需要调用后续拦截器管道,需要显式调用InvocationContext上下文的 ProceedAsync 方法。

四、两种注入方式

由于拦截器最终是利用.NET Core的依赖注入框架提供的,所以依赖服务可以直接注入拦截器的构造函数中。但是就服务的生命周期来讲,拦截器本质上是一个 Singleton 服务,我们不应该将 Scoped 服务注入到它的构造函数中。如果具有针对Scoped服务注入的需要,我们应该将它注入到InvokeAsync方法中。

public class FoobarInterceptor
{
    private readonly string _baz;

    public FoobarInterceptor(string baz)
    {
        _baz = baz;
    }

    public async InvokeAsync(InvocationContext context, IFoo foo, IBar bar)
    {
        await PreInvokeAsync();
        await context.ProceedAsync();
        await PostInvokeAsync();
    }
}

当Dora.Interception在调用InvokeAsync方法的时候,它会利用 当前Scope的IServiceProvider对象 来提供其参数。对于ASP.NET Core应用来说,如果拦截器的执行在整个请求处理的调用链中,这个IServiceProvider对象就是当前HttpContext的RequestServices属性。如果当前IServiceProvider不存在,作为根的IServiceProvider对象会被使用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK