服务注册》、《服务消费》和《生命周期》主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现。在过去的多个版本更迭过程中,依赖注入框架的底层实现一直都在发生改变,加上底层的涉及的大都是内容接口和类型,所以我们不打算涉及太过细节的层面。

一、ServiceProviderEngine & ServiceProviderEngineScope

对于依赖注入的底层设计和实现来说,ServiceProviderEngine和ServiceProviderEngineScope是两个最为核心的类型。顾名思义,ServiceProviderEngine表示提供服务实例的提供引擎,服务实例最终是通过该引擎提供的,在一个应用范围内只存在一个全局唯一的ServiceProviderEngine对象。ServiceProviderEngineScope代表服务范围,它利用对提供服务实例的缓存实现对生命周期的控制。ServiceProviderEngine实现了接口IServiceProviderEngine,从如下的代码片段可以看出,一个ServiceProviderEngine对象同时也是一个IServiceProvider对象,还是一个IServiceScopeFactory对象。

internal interface IServiceProviderEngine :  IServiceProvider, IDisposable, IAsyncDisposable
{
void ValidateService(ServiceDescriptor descriptor);
IServiceScope RootScope { get; }
} internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory
{
public IServiceScope RootScope { get; }
public IServiceScope CreateScope();
...
}

ServiceProviderEngine的RootScope属性返回的IServiceScope对象是为根容器提供的服务范围。作为一个IServiceScopeFactory对象,ServiceProviderEngine的CreateScope会创建一个新的服务范围,这两种服务范围都通过一个ServiceProviderEngineScope对象来表示。

internal class ServiceProviderEngineScope : IServiceScope, IDisposable,  IServiceProvider, IAsyncDisposable
{
public ServiceProviderEngine Engine { get; }
public IServiceProvider ServiceProvider { get; }
public object GetService(Type serviceType);
}

如上面的代码片段所示,一个ServiceProviderEngineScope对象不仅是一个IServiceScope对象,还是一个IServiceProvider对象。在《生命周期》中,我们说表示服务范围的IServiceScope对象是对一个表示依赖注入容器的IServiceProvider对象的封装,实际上两者合并为同一个ServiceProviderEngineScope对象,一个ServiceProviderEngineScope对象的ServiceProvider属性返回的就是它自己。换句话说,我们所谓的子容器和它所在的服务范围引用的都是同一个ServiceProviderEngineScope对象。

下图进一步揭示了ServiceProviderEngine和ServiceProviderEngineScope之间的关系。对于一个通过调用ServiceProviderEngine对象的CreateScope创建的ServiceProviderEngineScope来说,由于它同时也是一个IServiceProvider对象,如果我们调用它的GetService<IServiceProvider>方法,该方法同样返回它自己。如果我们调用它的GetService<IServiceScopeFactory>方法,它返回创建它的ServiceProviderEngine对象,也就是该方法和Engine属性返回同一个对象。

依赖注入框架提供的服务实例最终是通过ServiceProviderEngine对象提供的。从上面给出的代码片段可以看出,ServiceProviderEngine是一个抽象类,.NET Core依赖注入框架提供了如下四个具体的实现类型,默认使用的是DynamicServiceProviderEngine。

  • RuntimeServiceProviderEngine:采用反射的方式提供服务实例;
  • ILEmitServiceProviderEngine:采用IL Emit的方式提供服务实例;
  • ExpressionsServiceProviderEngine:采用表达式树的方式提供服务实例;
  • DynamicServiceProviderEngine:根据请求并发数量动态决定最终的服务实例提供方案(反射和者IL Emit或者反射与表达式树,是否选择IL Emit取决于当前运行时是否支持Reflection Emit)。

4.4.2. ServiceProvider

调用IServiceCollection集合的扩展方法BuildServiceProvider创建的是一个ServiceProvider对象。作为根容器的ServiceProvider对象,和前面介绍的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都是内部类型,我们只能采用反射的方式得到它们的属性或者字段成员。上面总结的这些特征体现在如下几组调试断言中。

class Program
{
static void Main()
{
var (engineType, engineScopeType) = ResolveTypes();
var root = new ServiceCollection().BuildServiceProvider();
var child1 = root.CreateScope().ServiceProvider;
var child2 = root.CreateScope().ServiceProvider; var engine = GetEngine(root);
var rootScope = GetRootScope(engine, engineType); //ServiceProviderEngine的唯一性
Debug.Assert(ReferenceEquals(GetEngine(rootScope, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child1, engineScopeType), engine));
Debug.Assert(ReferenceEquals(GetEngine(child2, engineScopeType), engine)); //ServiceProviderEngine和IServiceScopeFactory的同一性
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceScopeFactory>(), engine));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceScopeFactory>(), engine)); //ServiceProviderEngineScope提供的IServiceProvider是它自己
//ServiceProvider提供的IServiceProvider是RootScope
Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceProvider>(), rootScope));
Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceProvider>(), child1));
Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceProvider>(), child2)); //ServiceProviderEngineScope和IServiceProvider的同一性
Debug.Assert(ReferenceEquals((rootScope).ServiceProvider, rootScope));
Debug.Assert(ReferenceEquals(((IServiceScope)child1).ServiceProvider, child1));
Debug.Assert(ReferenceEquals(((IServiceScope)child2).ServiceProvider, child2));
} static (Type Engine, Type EngineScope) ResolveTypes()
{
var assembly = typeof(ServiceProvider).Assembly;
var engine = assembly.GetTypes().Single(it => it.Name == "IServiceProviderEngine");
var engineScope = assembly.GetTypes().Single(it => it.Name == "ServiceProviderEngineScope");
return (engine, engineScope);
} static object GetEngine(ServiceProvider serviceProvider)
{
var field = typeof(ServiceProvider).GetField("_engine", BindingFlags.Instance | BindingFlags.NonPublic);
return field.GetValue(serviceProvider);
} static object GetEngine(object enginScope, Type engineScopeType)
{
var property = engineScopeType.GetProperty("Engine", BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(enginScope);
} static IServiceScope GetRootScope(object engine, Type engineType)
{
var property = engineType.GetProperty("RootScope", BindingFlags.Instance | BindingFlags.Public);
return (IServiceScope)property.GetValue(engine);
}
}

三、注入IServiceProvider对象

在《依赖注入模式》中,我们从“Service Locator”设计模式是反模式的角度说明了为什么不推荐在服务中注入IServiceProvider对象。不过反模式并不就等于是完全不能用的模式,有些情况下直接在服务构造函数中注入作为依赖注入容器的IServiceProvider对象可能是最快捷省事的解决方案。对于IServiceProvider对象的注入,有个细节大家可能忽略或者误解。

读者朋友们可以试着思考这么一个问题:如果我们在某个服务中注入了IServiceProvider对象,当我们利用某个IServiceProvider对象来提供该服务实例的时候,注入的IServiceProvider对象是它自己吗?以如下所示的代码片段为例,我们定义了两个在构造函数中注入了IServiceProvider对象的服务类型SingletonService和ScopedService,并按照命名所示的生命周期进行了注册。

class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
} public class SingletonService
{
public IServiceProvider ApplicationServices { get; }
public SingletonService(IServiceProvider serviceProvider) => ApplicationServices = serviceProvider;
} public class ScopedService
{
public IServiceProvider RequestServices { get; }
public ScopedService(IServiceProvider serviceProvider) => RequestServices = serviceProvider;
}
}

我们最终利用一个作为子容器的IServiceProvider对象(ServiceProviderEngineScope对象)来提供这来个服务类型的实例,并通过调试断言确定注入的IServiceProvider对象是否就是作为当前依赖注入容器的ServiceProviderEngineScope对象。如果在Debug模式下运行上述的测试代码,我们会发现第一个断言是成立的,第二个则不成立。

再次回到两个服务类型的定义,SingletonService和ScopedService中通过注入IServiceProvider对象初始化的属性分别被命名为ApplicationServices和RequestServices,意味着它们希望注入的分别是针对当前应用程序的根容器和针对请求的子容器。当我们利用针对请求的子容器来提供针对这两个类型的服务实例时,如果注入的当前子容器的话,就与ApplicationServices的意图不符。所以在提供服务实例的注入的IServiceProvider对象取决于采用的生命周期模式,具体策略为:

  • Singleton:注入的是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。
  • Scoped和Transient:如果当前IServiceProvider对象类型为ServiceProviderEngineScope,注入的就是它自己,如果是一个ServiceProvider对象,注入的还是ServiceProviderEngine的RootScope属性表示的ServiceProviderEngineScope对象。

基于生命周期模式注入IServiceProvider对象的策略可以通过如下这个测试程序来验证。最后还有一点需要补充一下:我们将调用IServiceCollection集合的BuildServiceProvider扩展方法创建的ServiceProvider对象作为根容器,它对应的ServiceProviderEngine对象的RootScope属性返回作为根服务范围的ServiceProviderEngineScope对象,ServiceProvider、ServiceProviderEngine和ServiceProviderEngineScope这三个类型全部实现了IServiceProvider接口,这三个对象都可以视为根容器。

class Program
{
static void Main()
{
var serviceProvider = new ServiceCollection()
.AddSingleton<SingletonService>()
.AddScoped<ScopedService>()
.BuildServiceProvider();
var rootScope = serviceProvider.GetService<IServiceProvider>();
using (var scope = serviceProvider.CreateScope())
{
var child = scope.ServiceProvider;
var singletonService = child.GetRequiredService<SingletonService>();
var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, child.GetRequiredService<IServiceProvider>()));
Debug.Assert(ReferenceEquals(child, scopedService.RequestServices));
Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices));
}
}
}

[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述的更多相关文章

  1. [ASP.NET Core 3框架揭秘] 依赖注入:控制反转

    ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...

  2. [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务

    毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...

  3. [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期

    生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...

  4. [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

    .NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...

  5. [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费

    包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...

  6. [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册

    通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...

  7. [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架

    在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...

  8. [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...

  9. [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式

    正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...

随机推荐

  1. 【Luogu 1993】差分约束系统问题——小K的农场

    Luogu P1993 前置知识:最短路径相关算法 如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统. 显然题目中给出 ...

  2. oracle内存占用过高和修改不当无法启动oracle实例的解决办法

    今天,在自己机器上装了oracle 12c,发现Oracle的服务Oracle RDBMS Kenel  Executable (OracleServiceORCL)占用内存高达5G,本人电脑内存才1 ...

  3. scrapy抓取斗鱼APP主播信息

    如何进行APP抓包 首先确保手机和电脑连接的是同一个局域网(通过路由器转发的网络,校园网好像还有些问题). 1.安装抓包工具Fiddler,并进行配置 Tools>>options> ...

  4. moco框架应用一步到位

    1.  Moco部署 5.1         运行环境 ü   Java运行环境 ü   moco-runner-0.11.0-standalone.jar jar包: Windows Java环境配 ...

  5. Java工作流引擎全局变量的介绍

    关键词:工作流快速开发平台  工作流流设计  业务流程管理   asp.net 开源工作流bpm工作流系统  java工作流主流框架  自定义工作流引擎 在系统中有很多的地方需要用到表达式的地方,这些 ...

  6. Netty-主从Reactor多线程模式的源码实现

    Netty--主从Reactor多线程模式的源码实现 总览 EventLoopGroup到底是什么? EventLoopGroup是一个存储EventLoop的容器,同时他应该具备线程池的功能. gr ...

  7. 成功的面对对象语言的五个基本特性——Alan Kay

    1.万物皆为对象. 将对象是为奇特的变量,它可以存储数据,除此之外,还可以要求他在自身上执行操作. 2.程序是对象的集合,他们通过发送消息来告知彼此所要做的. 要想请求一个对象,就必须对该对项发送一条 ...

  8. KNN学习笔记

    简单地说,KNN算法就是通过测量不同特征值之间的距离来对特征进行分类的一种算法. 优点:精度高.对异常值不敏感.无数据输入假定. 缺点:计算复杂度高.空间复杂度高. 适用数据范围:数值型和标称型. 工 ...

  9. Spring面试题集锦(精选)

    以下来自网络收集,找不到原文出处.此次主要为了面试收集,希望对大家有所帮助~~~~ 1.什么是Spring? Spring是一个开源的Java EE开发框架.Spring框架的核心功能可以应用在任何J ...

  10. io流函数略解(java)[一]

    背景 最近在做安卓的过程中,因为im app经常涉及到读取与写入的io问题,所以总结一下.下文使用的是java语言. 实践 材料: java eclipse 1.File 在操作系统中我们一般能看到的 ...