上一篇《更加简练的编程体验》提供了最新版本的 Dora.Interception 代码的 AOP 编程体验, 接下来我们会这 AOP 框架的编程模式进行详细介绍, 本篇文章着重关注的是拦截器的定义. 采用 "基于约定" 的 Interceptor 定义方式是 Dora.Interception 区别于其他 AOP 框架的一个显著特征, 要了解拦截器的编程约定, 就得先来了解一下 Dora.Interception 中针对方法调用的拦截是如何实现的.
一, 针对实例的拦截
总地来说, Dora.Interception 针对方法调用的拦截机制分为两种类型, 我将它称为 "针对实例的拦截" 和 "针对类型" 的拦截. 针对实例的拦截应用于针对接口的方法调用, 其原理如下所示: 类型 Foobar 实现了接口 IFoobar, 如果需要拦截以接口的方式调用 Foobar 对象的某个方法, 我们可以动态生成另一个用来封装 Foobar 对象的 FoobarProxy 类型, FoobarProxy 同样实现 IFoobar 接口, 我们在实现的方法中实现对 Interceptor 链的调用. 我们最终将原始提供的 Foobar 对象封装成 FoobarProxy 对象, 那么针对 Foobar 的方法调用将转换成针对 FoobarProxy 对象的调用, 拦截得以实现.
二, 针对类型的拦截
如果 Foobar 并未实现任何接口, 或者针对它的调用并非以接口的方式进行, 那么我们只能采用 "针对类型的拦截", 其原理如下: 我们动态创建 Foobar 的派生类型 FoobarProxy, 并重写其需要被拦截的虚方法来实现对 Interceptor 链的调用. 我们最终创建 FoobarProxy 对象来替换掉原始的 Foobar 对象, 那么针对 Foobar 的方法调用将转换成针对 FoobarProxy 对象的调用, 拦截得以实现.
由于这种拦截方式会直接创建代理对象, 无法实现针对目标对象的封装, 当我们进行 DI 服务注册的时候, 只能指定注册服务的实现类型, 不能指定一个现有的 Singleton 实例或者提供一个创建实例的 Factory.
三, 从两个 Delegate 说起
要理解 Dora.Interception 的设计, 先得从如下这两个特殊的 Delegate 类型 (InterceptDelegate 和 InterceptorDelegate) 说起. InterceptDelegate 代表针对方法的拦截操作, 作为输入参数的 InvocationContext 提供了当前方法调用的所有上下文信息, 返回类型被设置为 Task 意味着 Dora.Interception 提供了针对基于 Task 的异步编程的支持.
- public delegate Task InterceptDelegate(InvocationContext context);
- public delegate InterceptDelegate InterceptorDelegate(InterceptDelegate next);
- public abstract class InvocationContext
- {
- public abstract object[] Arguments { get; }
- public abstract MethodBase Method { get; }
- public InterceptDelegate Next { get; }
- public abstract object Proxy { get; }
- public abstract object ReturnValue { get; set; }
- public abstract object Target { get; }
- public MethodBase TargetMethod { get; }
- public abstract IDictionary<string, object> ExtendedProperties { get; }
- public Task ProceedAsync();
- }
InterceptDelegate 表示的是 "拦截操作", 即表示作用于 InvocationContext 上下文上的一个 Task, 但它并不能表示一个拦截器对象. 原因很简单, 因为注册到同一个方法上的多个拦截器对象会构成一个链条, 最终决定是否调用后一个拦截器或者目标方法 (对于链条尾部的 Interceptor) 是由当前拦截器决定的, 所以如果将 Interceptor 也表示成委托对象, 它的输入应该是一个 InterceptDelegate 对象, 表示针对后一个拦截器或者目标方法的调用, 它返回的同样也是一个 InterceptDelegate 对象, 表示将自身纳入拦截器链之后, 新的拦截器链条 (包括调用目标方法) 所执行的操作.
所以一个 Interceptor 在 Dora.Interception 中应该表示成一个 Func<InterceptDelegate, InterceptDelegate > 对象, 这与 ASP.NET Core 的中间件管道其实是一回事. 简单起见, 我们为它专门定义了一个委托类型 InterceptorDelegate.
四, 将一个对象转换成 Interceptor
虽然 Dora.Interception 总是将 Interceptor 对象表示成上面介绍的 InterceptorDelegate 类型的委托, 但是为了更好的编程体验, 我们可以选择采用 POCO 类型的方法来定义 Interceptor. 为了提供更好的灵活性, 能够在方法中动态注入任意依赖服务, 我们并不打算为这样的 Interceptor 类型定义一个接口. 接口是一个契约, 同时也是一个限制. 如果类型实现某个接口, 意味着必需按照规定的声明实现其方法, 针对方法的服务注入将无法实现, 所以 Dora.Interception 采用 "基于约定" 的方式来定义 Interceptor 类型. 具体的约定如下
Interceptor 只需要定义一个普通的实例类型即可.
Interceptor 类型必须具有一个公共构造函数, 它可以包含任意的参数, 并支持构造器注入.
拦截功能实现在约定的 InvokeAsync 的方法中, 这是一个返回类型为 Task 的异步方法, 它的第一个参数类型为 InvocationContext.
除了表示当前执行上下文的参数之外, 任何可以注入的服务于对象都可以定义成 InvokeAsync 方法的参数.
当前 Interceptor 针对后续的 Interceptor 或者目标方法的调用通过调用 InvocationContext 的 ProceedAsync 方法来实现.
如下所示的就是一个典型的 Interceptor, 它提供了针对构造函数和方法的注入.
- public class FoobarInterceptor
- {
- public IFoo Foo { get; }
- public string Baz { get; }
- public FoobarInterceptor(IFoo foo, string baz)
- {
- Foo = foo;
- Baz = baz;
- }
- public async Task InvokeAsync(InvocationContext context, IBar bar)
- {
- await Foo.DoSomethingAsync();
- await bar.DoSomethingAsync();
- await context.ProceedAsync();
- }
- }
[1]: 更加简练的编程体验
[2]: 基于约定的拦截器定义方式
[3]: 多样性的拦截器应用方式
[4]: 与依赖注入框架的深度整合
[5]: 对拦截机制的灵活定制
来源: https://www.cnblogs.com/artech/p/dora-interception-02.html