4.4 实现概览
上面从实现原理的角度对.NET Core 的依赖注入框架进行了介绍,下面进一步介绍该框架的总体设计和实现。在过去的多个版本更迭过程中,依赖注入框架的底层实现一直都在发生改变,并且底层涉及的大多是内容接口和类型,所以本节不涉及太过细节的层面。
4.4.1 ServiceProviderEngine和ServiceProviderEngineScope
对于依赖注入的底层设计和实现来说,ServiceProviderEngine和ServiceProviderEngineScope是两个核心类型。顾名思义,ServiceProviderEngine 表示提供服务实例的提供引擎,容器提供的服务实例最终是通过该引擎提供的,在一个应用范围内只存在一个全局唯一的ServiceProviderEngine对象。ServiceProviderEngineScope代表服务范围,它利用对提供服务实例的缓存来实现对生命周期的控制。ServiceProviderEngine实现了IServiceProviderEngine接口,从如下代码片段可以看出,一个 ServiceProviderEngine 对象不仅是一个 IServiceProvider 对象,还是一个IServiceScopeFactory对象。
ServiceProviderEngine的RootScope属性返回的IServiceScope对象是为根容器提供服务范围的。作为一个 IServiceScopeFactory对象,ServiceProviderEngine的 CreateScope方法会创建一个新的服务范围,这两种服务范围都通过一个ServiceProviderEngineScope对象来表示。
如上面的代码片段所示,ServiceProviderEngineScope 对象不仅是一个 IServiceScope 对象,还是一个IServiceProvider对象。上面提及,表示服务范围的IServiceScope对象是对一个表示依赖注入容器的 IServiceProvider 对象的封装,实际上两者合并为同一个 ServiceProvider EngineScope对象,一个ServiceProviderEngineScope对象的ServiceProvider属性返回的就是它自己。换句话说,子容器和它所在的服务范围引用的是同一个 ServiceProviderEngineScope 对象。上述这些核心类型与接口之间的关系如图4-12所示。
图4-12 ServiceProviderEngine和ServiceProviderEngineScope
图 4-13进一步揭示了 ServiceProviderEngine和 ServiceProviderEngineScope之间的关系。对于一个通过调用 ServiceProviderEngine对象的 CreateScope创建的 ServiceProviderEngineScope来说,由于它也是一个IServiceProvider对象,所以如果调用它的GetService<IServiceProvider>方法,该方法同样返回它自己。如果调用它的 GetService<IServiceScopeFactory>方法,它返回创建它的ServiceProviderEngine对象,也就是该方法和Engine属性返回的是同一个对象。
图4-13 ServiceProviderEngine和ServiceProviderEngineScope之间的关系(一)
依赖注入框架提供的服务实例最终是通过 ServiceProviderEngine 对象提供的。从上面给出的代码片段可以看出,ServiceProviderEngine是一个抽象类,.NET Core依赖注入框架提供了如下4个具体的实现类型,默认使用的是DynamicServiceProviderEngine。
● RuntimeServiceProviderEngine:采用反射的方式提供服务实例。
● ILEmitServiceProviderEngine:采用IL Emit的方式提供服务实例。
● ExpressionsServiceProviderEngine:采用表达式树的方式提供服务实例。
● DynamicServiceProviderEngine:根据请求并发数量动态决定最终的服务实例提供方案(反射与 IL Emit 或者反射与表达式树,是否选择 IL Emit 取决于当前运行时是否支持Reflection Emit)。
4.4.2 ServiceProvider
调用IServiceCollection集合的BuildServiceProvider扩展方法创建的是一个ServiceProvider对象。如图4-14所示,作为根容器的ServiceProvider对象,与前面介绍的ServiceProviderEngine和ServiceProviderEngineScope对象共同构建整个依赖注入框架的设计蓝图。
图4-14 ServiceProviderEngine和ServiceProviderEngineScope之间的关系(二)
在利用 IServiceCollection 集合创建 ServiceProvider 对象的时候,提供的服务注册将用来创建一个具体的ServiceProviderEngine对象。该ServiceProviderEngine对象的RootScope就是它创建的一个ServiceProviderEngineScope对象,子容器提供的Singleton服务实例由它维护。如果调用 ServiceProvider 对象的 GetService<IServiceProvider>方法,返回的其实不是它自己,而是作为RootScope的ServiceProviderEngineScope对象(调用ServiceProviderEngineScope对象的GetService<IServiceProvider>方法返回的是它自己)。
ServiceProvider和 ServiceProviderEngineScope都实现了 IServiceProvider接口,如果调用了它们的 GetService<IServiceScopeFactory>方法,返回的就是同一个 ServiceProviderEngine对象。该特性决定了调用它们的CreateScope扩展方法会创建一个新的ServiceProviderEngineScope对象作为子容器。综上所述,依赖注入框架具有如下几方面特性。
● ServiceProviderEngine 的唯一性:整个服务提供体系只存在一个 ServiceProviderEngine对象。
● ServiceProviderEngine与IServiceFactory的同一性:唯一存在的ServiceProviderEngine会作为创建服务范围的IServiceFactory工厂。
● ServiceProviderEngineScope 和 IServiceProvider 的同一性:表示服务范围的ServiceProviderEngineScope也作为服务提供者的依赖注入容器。
为了印证依赖注入框架的特性,我们编写了如下所示的测试代码。由于ServiceProviderEngine和 ServiceProviderEngineScope 都是内部类型,所以只能采用反射的方式得到它们的属性或者字段成员。上面总结的这些特征体现在如下几组调试断言中。(S410)
4.4.3 注入IServiceProvider对象
第 3章从 Service Locator模式是反模式的角度说明了不推荐在服务中注入 IServiceProvider对象的原因。但反模式并不等于就是完全不能用的模式,有时直接在服务构造函数中注入作为依赖注入容器的IServiceProvider对象可能是最快捷、最省事的解决方案。对于IServiceProvider对象的注入,如下细节可能会被人们忽略或者误解。
请读者思考如下问题:如果在某个服务中注入了 IServiceProvider 对象,那么利用某个IServiceProvider对象提供该服务实例的时候,注入的 IServiceProvider对象是否是它自己?如下代码片段定义了两个在构造函数中注入了 IServiceProvider 对象的服务类型 SingletonService 和ScopedService,并按照命名所示的生命周期进行了注册。
我们最终利用一个作为子容器的 IServiceProvider 对象(ServiceProviderEngineScope 对象)来提供这两个服务类型的实例,并通过调试断言确定注入的 IServiceProvider 对象是否就是作为当前依赖注入容器的 ServiceProviderEngineScope对象。如果在 Debug模式下运行上述测试代码可以发现,第一个断言是成立的,第二个断言是不成立的。
再次回到两个服务类型的定义,SingletonService 类型和 ScopedService 类型中通过注入IServiceProvider对象初始化的属性分别被命名为ApplicationServices和RequestServices,意味着它们希望注入的分别是针对当前应用程序的根容器和针对请求的子容器。所以,利用针对请求的子容器来提供针对这两个类型的服务实例时,如果注入的是当前子容器,就与ApplicationServices的意图不符。由此可知,在提供服务实例时注入的IServiceProvider对象取决于采用的生命周期模式,具体策略如下。
● Singleton:注入的是 ServiceProviderEngine 的 RootScope 属性表示的 ServiceProvider EngineScope对象。
● Scoped和Transient:如果当前IServiceProvider对象的类型为ServiceProviderEngineScope,注入的就是它自己;如果是一个 ServiceProvider 对象,注入的就是 ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
基于生命周期模式注入 IServiceProvider 对象的策略可以通过如下测试程序进行验证(S411)。另外,如果将调用 IServiceCollection 集合的 BuildServiceProvider 扩展方法创建的ServiceProvider对象作为根容器,那么它对应的 ServiceProviderEngine对象的 RootScope属性返回作为根服务范围的 ServiceProviderEngineScope 对象,ServiceProvider、ServiceProviderEngine和ServiceProviderEngineScope这3个类型全部实现了IServiceProvider接口,可以将这3个对象都视为根容器。