服务注册》、《服务消费》和《生命周期》主要从实现原理的角度对.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. axios 请求二次封装

    /** * 封装get方法 * @param url * @param data * @returns {Promise} */ export function get(url, params) { ...

  2. 探索 IPv6 网络

    目录 0x00 前言 0x01 探索 服务器配置 IPv6 地址 服务器部署网络代理 客户端配置网络代理 测试访问 IPv6 地址 给博客添加 IPv6 地址 0x00 前言 IPv4 地址枯竭的事情 ...

  3. 【POJ 2823】【Luogu P1886】Sliding Window 滑动窗口

    POJ 2823 Luogu P1886 [解题思路] 这是一个单调队列算法的经典题目,几乎学习单调队列的人都接触过这题. 利用单调队列算法求出每一个固定区间内的最(大/小)值. 以下以最大值为例: ...

  4. Shell - 长 ping 脚本监控网络时延

    生产环境中, 网络时延是一个很重要的指标. 为了方便检查网络时延的大小, 我们可以通过ping命令实现长时间的网络监控. 1 ping 命令的使用 1.1 常用参数 -i: 每次执行ping操作的间隔 ...

  5. day 36 html的补充

    参考博客:https://www.cnblogs.com/majj/p/9062540.html 内容回顾: 0.浏览器 1.标签 - 行内标签 a span i em strong b.label ...

  6. css三大特效之继承性

    css三大特效之继承性

  7. Java语法进阶10-多线程

    多线程 并发与并行.进程,线程调度自行百度 线程(thread):是一个进程中的其中一条执行路径,CPU调度的最基本调度的单位.同一个进程中线程可以共享一些内存(堆.方法区),每一个线程又有自己的独立 ...

  8. Vue组件通信之非父子组件传值

    前言: 如果想要了解非父子关系的组件传值,最好是在了解父传子和子传父的基础上在来了解非父子传值可能会有更透彻的思路. 因为非父子传值是通过定义事件总线来代理实现父传子+子传父从而实现的传值方式. 这是 ...

  9. centos 7 Atlas keepalived 实现高可用 MySQL 5.7 MHA环境读写分离

    目录 简介 相关链接 环境准备 Atlas 环境 MySQL 集群环境 Atlas 安装 和 配置 为数据库的密码加密 修改配置文件 启动 Keepalived 安装配置 安装 master 配置 K ...

  10. iptables filter表案例、iptables nat表应用 使用介绍

    第7周第4次课(5月10日) 课程内容: 10.15 iptables filter表案例10.16/10.17/10.18 iptables nat表应用 扩展1. iptables应用在一个网段 ...