🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
本系列前面的五篇文章主要介绍6折优惠,首印送签名专属书签)。
目录 一、调用链抽象 二、基于约定的拦截器定义 三、基于调用上下文的依赖注入容器 四、拦截器的提供 五、调用链的构建 六、方法拦截的实现原理 七、依赖注入框架的整合 八、看看生成的代理类
一、调用链抽象
从设计模式来看,Dora.Interception采用了“职责链”模式。我们将应用到同一个方法的多个拦截器以及针对目标方法的调用构建成如下所示的“调用链”。调用链在执行过程中共享同一个“调用上下文”,后者提供当前调用的上下文信息,比如目标对象、调用方法、输出参数和返回值等。每个拦截器不仅可以利用这些上下文信息执行对应的操作,还可以直接利用此上下文修改参数和返回值,并且自行决定是否继续执行后续调用。
我们定义了如下这个抽象类InvocationContext来表示上述的调用上下文。对于参数/返回值的提取,我们设计成抽象方法以避免因装箱/拆箱带来的性能问题。拦截器针对其他服务的依赖是一个基本的需求,所以我们为InvocationContext定义了一个InvocationServices属性来提供针对当前调用的IServiceProvider对象。在默认情况下,我们会为每次调用创建一个服务范围,并利用此范围的IServiceProvider对象作为这个InvocationServices属性的值。但是对于ASP.NET Core应用,我们会直接使用针对当前请求的IServiceProvider对象。
public abstract class InvocationContext
{
public object Target { get; } = default!;
public abstract MethodInfo MethodInfo { get; }
public abstract IServiceProvider InvocationServices { get; }
public IDictionary<object, object> Properties { get; }
public abstract TArgument GetArgument(string name);
public abstract TArgument GetArgument(int index);
public abstract InvocationContext SetArgument(string name, TArgument value);
public abstract InvocationContext SetArgument(int index, TArgument value);
public abstract TReturnValue GetReturnValue();
public abstract InvocationContext SetReturnValue(TReturnValue value);
protected InvocationContext(object target);
internal InvokeDelegate Next { get; set; } = default!;
public ValueTask ProceedAsync() => Next.Invoke(this);
}
既然有了这样一个能够体现当前方法调用上下文的InvocationContext类型,那么上述的“调用量”就可以表示成如下这个InvokeDelegate委托。熟悉ASP.NET Core的读者可以看出Dora.Interception的调用链设计与ASP.NET Core框架的“中间件管道”几乎一致,InvocationContext和InvokeDelegate分别对应后者的HttpContext和RequestDelegate。
public delegate ValueTask InvokeDelegate(InvocationContext context);
既然将ASP.NET Core作为类比,Dora.Interception的拦截器自然就对应着ASP.NET Core的中间件了。我们知道后者体现为一个Func委托,作为输入的RequestDelegate代表由后续中间件构建的请求处理管道,每个中间件需要利用此对象将请求分发给后续管道进行处理。Dora.Interception采用了更为简单的设计,我们将拦截器也表示成上述的InvokeDelegate委托,因为针对后续拦截器以及目标方法的调用可以利用代表调用上下文的InvocationContext对象的ProceedAsync方法来完成。如上面的代码片段所示,InvocationContext具有一个名为Next的内部属性用来表示调用调用链的下一个InvokeDelegate对象,每个拦截器在执行之前,此属性都会预先被设置,ProceedAsync方法调用的正式此属性返回的InvokeDelegate对象。
二、基于约定的拦截器定义
虽然拦截器最终由一个InvokeDelegate委托来表示,但是将其定义成一个普通的类型具有更好的编程体验。考虑到动态注入依赖服务的需要,我们并没有为拦截器定义任何的接口和基类,而是采用基于约定的定义方式。这一点与ASP.NET Core基于约定的中间件定义方法类似,由于我们的拦截器委托比中间件委托要简洁,基于约定的拦截器自然比定义中间件要简单。中间件定义按照如下的约定即可:
- 将中间件定义成一个可以被依赖注入容器实例化的类型,一般定义成公共实例类型即可;
- 构造函数的选择由依赖注入容器决定,构造函数可以包含任意参数;
- 拦截操作定义在一个方法类型为ValueTask并被命名为InvokeAsync的异步方法中,该方法必须包含一个表示当前调用上下文的InvocationContext类型的参数,该参数在参数列表的位置可以任意指定。
- InvokeAsync方法可以注入任意能够从依赖注入容器提供的对象。
按照约定定义的中间件类型或者此类型的对象最终都需要转换成一个InvokeDelegate对象,此项功能体现在IConventionalInterceptorFactory接口的两个CreateInterceptor重载方法上。第一个重载的arguments将被作为调用构造函数的参数,对于依赖注入容器无法提供的参数必须在此指定。内部类型ConventionalInterceptorFactory以表达式树的形式实现了这个接口,具体实现就不在这里展示了,有兴趣的朋友可以查看源代码。
public interface IConventionalInterceptorFactory
{
InvokeDelegate CreateInterceptor(Type interceptorType, params object[] arguments);
InvokeDelegate CreateInterceptor(object interceptor);
}
internal sealed class ConventionalInterceptorFactory : IConventionalInterceptorFactory
{
public InvokeDelegate CreateInterceptor(Type interceptorType, params object[] arguments);
public InvokeDelegate CreateInterceptor(object interceptor);
}
三、基于调用上下文的依赖注入容器
InvocationContext的InvocationServices属性返回针对当前调用上下文的依赖注入容器。在默认的情况下,我们会在创建InvocationContext上下文的时候创建一个服务范围,并使用此范围的IServiceProvider对象作为其InvocationServices属性。注入到InvokeAsync方法中的依赖服务是在调用时利用此IServiceProvider对象动态提供的,我们也可以在实现的InvokeAsync方法中安全的使用此对象来提供所需的服务实例。由于服务范围会在调用结束之后被自动终结,所以非单例服务实例能够被正常回收。
如下所示的IInvocationServiceScopeFactory接口表示用来创建上述服务范围的工厂,代表服务范围的IServiceScope对象由其CreateInvocationScope方法创建,InvocationServiceScopeFactory是对该接口的默认实现。
public interface IInvocationServiceScopeFactory
{
IServiceScope CreateInvocationScope();
}
internal class InvocationServiceScopeFactory : IInvocationServiceScopeFactory
{
private readonly IApplicationServicesAccessor \_applicationServicesAccessor;
public InvocationServiceScopeFactory(IApplicationServicesAccessor applicationServicesAccessor) => \_applicationServicesAccessor = applicationServicesAccessor ?? throw new ArgumentNullException(nameof(applicationServicesAccessor));
public IServiceScope CreateInvocationScope()=> \_applicationServicesAccessor.ApplicationServices.CreateScope();
}
如果在一个ASP.NET Core应用中,我们因为针对当前请求的IServiceProvider(RequestServices)对象作为调用上下文的InvocationServices也许更为适合,所以在ASP.NET Core应用中注册的IInvocationServiceScopeFactory实现类型为如下这个RequestServiceScopeFactory 类型。
internal class RequestServiceScopeFactory : IInvocationServiceScopeFactory
{
private readonly InvocationServiceScopeFactory \_factory;
private readonly IHttpContextAccessor \_httpContextAccessor;
private NullServiceScope? \_nullServiceScope;
public RequestServiceScopeFactory(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor)
{
\_factory = ActivatorUtilities.CreateInstance(serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)));
\_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public IServiceScope CreateInvocationScope()
{
\_nullServiceScope ??= new NullServiceScope (\_httpContextAccessor);
return \_httpContextAccessor.HttpContext == null? \_factory.CreateInvocationScope(): \_nullServiceScope;
}
private class NullServiceScope : IServiceScope
{
private readonly IHttpContextAccessor \_httpContextAccessor;
public NullServiceScope(IHttpContextAccessor httpContextAccessor) => \_httpContextAccessor = httpContextAccessor;
public IServiceProvider ServiceProvider => \_httpContextAccessor.HttpContext?.RequestServices!;
public void Dispose() { }
}
}
四、拦截器的提供
我们利用如下这个IInterceptorProvider接口来表示拦截器的“提供者”,它定义的GetInterceptors方法为指定类型的方法提供一组可供排序的拦截器,该方法返回一组Sortable对象,每个Sortable对象的Value属性代表作为拦截器的InvokeDelegate委托,Order属性用来对拦截器进行排序。
public interface IInterceptorProvider
{
bool CanIntercept(Type targetType, MethodInfo method, out bool suppressed);
IEnumerable> GetInterceptors(Type targetType, MethodInfo method);
void Validate(Type targetType, Action methodValidator, Action propertyValidator) ;
}
public sealed class Sortable
{
public int Order { get; }
public T Value { get; set; }
public Sortable(int order, T value)
{
Order = order;
Value = value;
}
}
IInterceptorProvider旨在为指定的方法提供拦截器,所以它体现的是针对拦截器的注册,即采用怎样的方式将拦截器应用到期望的目标方法上。根据Dora.Interception的实现原理,并不是每一个方法都能被拦截,所以我们为IInterceptorProvider定义了一个Validate方法用来验证被应用到指定方法或者属性上的拦截器是否有效。具体的验证逻辑无需自行实现,只需要调用该方法提供的两个作为验证器的参数(methodValidator和propertyValidator)就可以了。这样做的好处是今早确定我们针对某个方法的拦截意图是否能够生效,Dora.Interception提供的两种原生的实现均实现了验证功能,对于自定义的实现,可以根据需要决定是否需要验证。
IInterceptorProvider接口还定义了CanIntercept方法用来确定指定类型的方法能否被拦截。一般来说,如果指定方法上没