[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了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]:服务实例的生命周期的更多相关文章
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[5]: 利用容器提供服务
毫不夸张地说,整个ASP.NET Core框架是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道以及利用该管道处理每个请求过程中使用到的服务对象均来源于依赖注入容器.该依赖注入容 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配
.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
<服务注册>.<服务消费>和<生命周期>主要从实现原理的角度对.NET Core的依赖注入框架进行了介绍,接下来更进一步,看看该框架的总体设计和实现.在过去的多个版 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...
- [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[4]:一个Mini版的依赖注入框架
在前面的章节中,我们从纯理论的角度对依赖注入进行了深入论述,我们接下来会对.NET Core依赖注入框架进行单独介绍.为了让读者朋友能够更好地理解.NET Core依赖注入框架的设计与实现,我们按照类 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照"好莱坞法则"实现应用程序的代码与框架之间的交互.我们可以采用若干设计模式 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种"面向对象的设计模式",实际上IoC不仅与面向对象没有必然的联系,它自身甚至算不上是一种设计模式.一般 ...
随机推荐
- Android View 的添加绘制流程 (二)
概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最 ...
- vue当文字很多的时候实现...代替
vue当文字很多的时候实现...代替只需加三行代码 overflow: hiddenwhite-space: nowraptext-overflow: ellipsis
- java中sleep()和wait()区别
1,sleep方法是Thread类的静态方法,wait()是Object超类的成员方法 2,sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时 ...
- logistic回归介绍以及原理分析
1.什么是logistic回归? logistic回归虽然说是回归,但确是为了解决分类问题,是二分类任务的首选方法,简单来说,输出结果不是0就是1 举个简单的例子: 癌症检测:这种算法输入病理图片并且 ...
- Tab Bar Control 的封装和切换
见视频0414 思路: 1.删除系统自带的TabBar.2.添加UIView,做成自定义的TabBar,覆盖原来的TabBar.3.添加对于的button和切换事件.
- Jenkins修改默认主目录及数据迁移
前言 在使用Jenkins做持续集成的初期,未能预估项目量的大小.于是乎,配置都是使用的默认配置,而Jenkins的默认主目录放在了服务器的根目录下. 随着时间的推移,项目量的持续增加,在运维过程中就 ...
- Linux的ftp安装及使用
FTP服务器的安装与配置(Ubuntu)1.查询是否安装vsftpd: rpm -qa |grep vsftpd (rpm的安装:apt-get install rpm) 或者查询当前ftp进程:p ...
- Eclipse官方下载步骤
今天整理Eclipse项目时,发现自己的IDE不能用了,不兼容自己的JDK,于是决定去官网下载一个适合的IDE,由于官网全部都是英文,所以不是太容易找到,于是就想着出一篇博客帮助以后的人更好的更快的下 ...
- IDEA IntelliJ/ DataGrip 修改自动补全快捷键
系统默认的是Tab键,个人喜欢用空格键作为自动补全键,设置方法如下 Setting-->Keymap-->Editor Actions:Choose Lookup Item Replace ...
- spring boot 中@Mapper和@Repository的区别
0--前言 @Mapper和@Repository是常用的两个注解,两者都是用在dao上,两者功能差不多,容易混淆,有必要清楚其细微区别: 1--区别 @Repository需要在Spring中配置扫 ...