生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例。虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的。在我们提供的依赖注入框架Cat中,我们已经模拟了三种生命周期模式的实现原理,接下来我们结合“服务范围”的概念来对这个话题做进一步讲述。

一、服务范围(Service Scope)

对于依赖注入框架采用的三种生命周期模式(Singleton、Scoped和Transient)来说,Singleton和Transient都具有明确的语义,但是Scoped代表一种怎样的生命周期模式,很多初学者往往搞不清楚。这里所谓的Scope指的是由IServiceScope接口表示的“服务范围”,该范围由IServiceScopeFactory接口表示的“服务范围工厂”来创建。如下面的代码片段所示,IServiceProvider的扩展方法CreateScope正是利用提供的IServiceScopeFactory服务实例来创建作为服务范围的IServiceScope对象。

public interface IServiceScope : IDisposable
{
IServiceProvider ServiceProvider { get; }
} public interface IServiceScopeFactory
{
IServiceScope CreateScope();
} public static class ServiceProviderServiceExtensions
{
public static IServiceScope CreateScope(this IServiceProvider provider) => provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

任何一个IServiceProvider对象都可以利用其注册的IServiceScopeFactory服务创建一个代表服务范围的IServiceScope对象,后者代表的“范围”内具有一个新创建的IServiceProvider对象(对应着接口IServiceScope的ServiceProvider属性),该对象与当前IServiceProvider在逻辑上具有如下图所示的“父子关系”。

如上图所示的树形层次结构只是一种逻辑结构,从对象引用层面来看,通过某个IServiceScope封装的IServiceProvider对象不需要知道自己的“父亲”是谁,它只关心作为根节点的IServiceProvider在哪里就可以了。下图从物理层面揭示了IServiceScope / IServiceProvider对象之间的关系,任何一个IServiceProvider对象都具有针对根容器的引用。

二、服务实例的提供

只有在充分了解IServiceScope对象的创建过程以及它与IServiceProvider对象之间的关系之后,我们才会对三种生命周期管理模式(Singleton、Scoped和Transient)具有深刻的认识。就服务实例的提供方式来说,它们之间具有如下的差异:

  • Singleton:IServiceProvider对象创建的服务实例保存在作为根容器的IServiceProvider对象上,所以多个同根的IServiceProvider对象提供的针对同一类型的服务实例都是同一个对象。
  • Scoped:IServiceProvider对象创建的服务实例由自己保存,所以同一个IServiceProvider对象提供的针对同一类型的服务实例均是同一个对象。
  • Transient:针对每一次服务提供请求,IServiceProvider对象总是创建一个新的服务实例。

三、服务实例的释放

IServiceProvider除了为我们提供所需的服务实例之外,对于由它提供的服务实例,它还肩负起回收释放的责任。这里所说的回收释放与.NET Core自身的垃圾回收机制无关,仅仅针对于自身类型实现了IDisposable或者IAsyncDisposable接口的服务实例(下面简称为Disposable服务实例),针对服务实例的释放体现为调用它们的Dispose或者DisposeAsync方法。IServiceProvider对象针对服务实例采用的回收释放策略取决于采用的生命周期模式,具体策略主要体现为如下两点:

  • Singleton:提供Disposable服务实例保存在作为根容器的IServiceProvider对象上,只有在这个IServiceProvider对象被释放的时候这些Disposable服务实例才能被释放。
  • Scoped和Transient:当前IServiceProvider对象会保存由它提供的Disposable服务实例,当自己被释放的时候,这些Disposable服务实例就会被释放。

综上所述,每个作为依赖注入容器的IServiceProvider对象都具有如下图所示的两个列表来存放服务实例,我们将它们分别命名为“Realized Services”和“Disposable Services”,对于一个作为非根容器的IServiceProvider对象来说,由它提供的Scoped服务保存在自身的Realized Services列表中,Singleton服务实例则会保存在根容器的Realized Services列表中。如果服务实现类型实现了IDisposable或者IAsyncDisposable接口,Scoped和Transient服务实例会被保存到自身的Disposable Services列表中,而Singleton服务实例则会保存到根容器的Disposable Services列表中。

对于作为根容器的IServiceProvider对象来说,Singleton和Scoped模式对它来说是两种等效的生命周期模式,由它提供的Singleton和Scoped服务实例会被存放到自身的Realized Services列表中,而所有需要被释放的服务实例则被存放到Disposable Services列表中。当某个IServiceProvider对象被用于提供针对指定类型的服务实例时,它会根据服务类型提取出表示服务注册的ServiceDescriptor对象并根据它得到对应的生命周期模式:

  • 如果生命周期模式为Singleton,并且作为根容器的Realized Services列表中包含对应的服务实例,它将作为最终提供的服务实例。如果这样的服务实例尚未创建,那么新的服务将会被创建出来并作为提供的服务实例。这个服务实例会被添加到根容器的Realized Services列表中。如果实例类型实现了IDisposable或者IAsyncDisposable接口,创建的服务实例会被添加到根容器的Disposable Services列表中。
  • 如果生命周期为Scoped,那么IServiceProvider会先确定自身的Realized Services列表中是否存在对应的服务实例,存在的服务实例将作为最终的返回值。如果Realized Services列表不存在对应的服务实例,那么新的服务实例会被创建出来。在作为最终的服务实例被返回之前,创建的服务实例会被添加到自身的Realized Services列表中,如果实例类型实现了IDisposable或者IAsyncDisposable接口,创建的服务实例会被添加到自身的Disposable Services列表中。
  • 如果提供服务的生命周期为Transient,那么IServiceProvider会直接创建一个新的服务实例。在作为最终的服务实例被返回之前,如果实例类型实现了IDisposable或者IAsyncDisposable接口,创建的服务实例会被添加到自身的Disposable Services列表中。

对于非根容器的IServiceProvider对象来说,它的生命周期是由“包裹”着它的IServiceScope对象控制的。从前面给出的定义可以看出IServiceScope实现了IDisposable接口,Dispose方法的执行不仅标志着当前服务范围的终结,也意味着对应IServiceProvider对象生命周期的结束。

当代表服务范围的IServiceScope对象的Dispose方法被调用的时候,它会调用对应IServiceProvider对象的Dispose方法。一旦IServiceProvider对象因自身Dispose方法的调用而被释放的时候,它会从自身的Disposable Services列表中提取出所有需要被释放的服务实例,并调用它们的Dispose或者DisposeAsync方法。在这之后,Disposable Services和Realized Services列表会被清空,列表中的服务实例和IServiceProvider对象自身会成为垃圾对象被GC回收。

四、ASP.NET Core应用

依赖注入框架所谓的服务范围在ASP.NET Core应用中具有明确的边界,指的是针对每个HTTP请求的上下文,也就是服务范围的生命周期与每个请求上下文绑定在一起。如下图所示,ASP.NET Core应用中用于提供服务实例的IServiceProvider对象分为两种类型,一种是作为根容器并与应用具有相同生命周期的IServiceProvider对象,另一个类则是根据请求及时创建和释放的IServiceProvider对象,我们一般将它们分别称为ApplicationServices和RequestServices。

在ASP.NET Core应用初始化过程(即请求管道构建过程)中使用的服务实例都是由ApplicationServices提供的。在具体处理每个请求时,ASP.NET Core框架会利用注册的一个中间件来针对当前请求创建一个代表服务范围的IServiceScope对象,该服务范围提供的RequestServices用来提供当前请求处理过程中所需的服务实例。一旦服务请求处理完成,IServiceScoped对象代表的服务范围被终结,在当前请求处理过程中的Scoped服务会变成垃圾对象并最终被GC回收。对于实现了IDisposable或者IAsyncDisposable接口的Scoped或者Transient服务实例来说,在变成垃圾对象之前,它们的Dispose或者DisposeAsync方法会被调用。

[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框架揭秘] 依赖注入[8]:服务实例的生命周期的更多相关文章

  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框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

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

  4. [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述

    <服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.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. H3C交换机console登录配置 v7

    一.通过con口只需输入password登陆交换机. [H3C]user-interface aux 0 设置认证方式为密码验证方式 [H3C-ui-aux0] authentication-mode ...

  2. 2019-9-25:渗透测试,基础学习,Hydra BP爆破,js基本知识,banner信息收集笔记

    使用BP和Hydra爆破相关的服务hydra:九头蛇,开源的功能强大的爆破工具,支持的服务有很多,使用hydra爆破c/s结构的服务,使用bp爆破web登陆窗口爆破需要的几个条件,爆破工具+字典字典: ...

  3. webpackd学习的意义

    高速发展的前端技术,与浏览器支持的不相匹配.导致前端必须把前端比较先进的技术进行一层编码从而使得浏览器可以加载. 比如前端框架Vue,Angular,React.Less,Sass.TypeScrip ...

  4. Java包package之间调用问题-cmd运行窗口编译运行

    问题:在使用了java包机制(package)后,编译出现错误:找不到或无法加载主类 xxx 的错误提示信息(各种编译不通过) 先给演示结果: 编译:javac -d classes src/a/He ...

  5. vue JS实现监听浏览器返回按键事件

    // 这个是监听浏览器回退键的returnButton () { let vm = this; $(document).ready(function () { if (window.history & ...

  6. phpstorm2019激活码

    6ZUMD7WWWU-eyJsaWNlbnNlSWQiOiI2WlVNRDdXV1dVIiwibGljZW5zZWVOYW1lIjoiSmV0cyBHcm91cCIsImFzc2lnbmVlTmFtZ ...

  7. altium designer 20.0.8

    altium designer 20.0.8 download : http://dl3.downloadly.ir/Files/Software/Altium_Designer_20.0.8_Bet ...

  8. Stream系列(十二) PartitioningBy方法使用

    分割列表 视频讲解: https://www.bilibili.com/video/av78106120/ EmployeeTestCase.java package com.example.demo ...

  9. 【Android - 进阶】之Animation补间动画

    补间动画也叫View动画,它只能针对View进行动画操作,且补间动画操作的只是View中可见的部分,即只操作界面,对于可点击区域等都不会进行操作. 在Android中,补间动画的顶级类是Animati ...

  10. 浅析vue混入(mixin)

    vue中的混入,可以在一定程度上提高代码的复用性.通俗来说,混入类似于“继承”,当前组件对象继承于组件对象,一般情况下遵循“就近原则”.但是与继承不同的是,继承一般都跟随着属性的重写与合并,混入在不同 ...