- A+
在现实生活中,生命周期一词往往代表着某些人或事物从生到死的过程,而在依赖注入框架中,生命周期中的“生与死”体现为服务实例的创建和释放。实际上对于介绍依赖注入框架的生命周期而言,就是在介绍依赖注入容器采用什么样的方式创建和释放服务实例。
多个容器之间的组织结构
在介绍生命周期之前,我们必须先对“多个容器之间的组织结构”和“服务范围”有一个基本的了解,因为某类生命周期模式的服务实例和“服务范围”息息相关的,如果该模式的服务实例不在“服务范围”进行使用,那么就失去了使用该模式的意义。“你知道我说的是哪个模式吗?”
在依赖注入框架中容器并不是作为一个单一化的结构存在。最开始会初始化一个根容器,我们可以利用根容器创建一个服务范围,创建的服务范围中就会包含一个容器,这种容器我们又称为子容器。基于这种创建方式,我们必须清楚知道一点的是:除了根容器之外,所有的子容器都包含在一个服务范围中。而对于每个服务范围而言,它可以不断向下延申创建更多服务范围,从而获得更多的子容器。以这种类似于“父子关系”不断向下延申创建,最终从表示上来看多个容器的组织会形成一个树形层次化的结构。
而对于上图中这种树形层次化的结构,仅仅是我们根据容器的创建形式主观意识上的一种逻辑结构,并非是依赖注入框架中实际的容器层次结构。从各个容器对象的引用层面来看,服务范围中的容器对象(子容器)并不需要知道自己的“父容器”是谁,它只在乎根容器对象在哪里,所以每个子容器对象只会针对根容器对象进行引用,根据这种引用情况多个容器之间的组织结构如下图所示。
在代码中如果要创建一个子容器,首先需要获取ServiceCollection对象,并调用该对象的BuildServiceProvider方法构建出根容器对象。然后使用IServiceProvider接口类型的根容器对象调用CreateScope扩展方法,该扩展方法会使用IServiceScopeFactory(服务范围工厂)来创建一个IServiceScope类型的服务范围对象,而这个服务范围对象中的ServiceProvider属性就是创建的子容器。代码创建过程如下:
1 static void Main(string[] args) 2 { 3 //服务注册信息集合 4 var serviceCollextion = new ServiceCollection(); 5 6 //构建根容器对象 7 IServiceProvider rootProvider = serviceCollextion.BuildServiceProvider(); 8 9 //创建服务范围 10 IServiceScope scope = rootProvider.CreateScope(); 11 12 //获取子容器对象 13 IServiceProvider subProvider = scope.ServiceProvider; 14 15 } // END Main()
在ASP.NET Core应用中根容器对象称为ApplicationService,服务范围中的子容器对象被称为RequesetService。对于“服务范围”的概念其实就相当于,客户端向服务器发送的一次HTTP请求范围。在具体处理每个请求时,ASP.NET Core框架会利用注册的一个中间件来针对当前请求创建一个代表服务范围的IServiceScope对象,该服务范围对象提供的容器对象RequesetService,将用来提供当前HTTP请求处理过程中所需的服务实例。当HTTP请求处理完成后,对应的服务范围将会被终结,服务范围其下的容器对象以及容器中的服务实例都会被变成垃圾对象待GC进行回收。
创建
基于“多个容器之间的组织结构”和“服务范围”的概念,促成了依赖注入框架中服务实例的3种生命周期模式:
- Singleton(单例);
- Scoped(范围);
- Transient(暂时);
接下来,我们首先根据这3种生命周期模式,在创建服务实例上的特点进行介绍。
Singleton:该模式的服务实例只会创建一次,保存在根容器中,并且由根容器提供。在多个同根的子容器中可以共同访问,并且不同子容器对于同一类型的访问,获取的是根容器中同一个实例,相当于单例模式的对象。
Scoped:该模式的服务实例在同一个服务范围的容器中只会创建一次,在不同的服务范围的容器中会多次创建,不同服务范围的容器保存各自创建的服务实例。在同一个服务范围的容器中,访问同一类型的实例都属于同一个,相当于在服务范围内的单例模式对象。该模式需要注意的是:如果使用根容器提供Scoped模式的服务实例,那么Scoped模式将会变成为Singleton模式。
Transient:该模式的服务实例不会在容器中进行保存。所以容器对象每次提供时都会创建一个新的服务实例,有着“即用即建,用后即弃”的特点。
释放
依赖注入框架中容器对服务实例的释放与.NET Core中的垃圾回收机制并不是一回事,容器对服务实例的释放仅仅针对的是实现了IDisposable接口的服务实例,具体的释放操作实际上就是调用它们的Dispose方法,而不同的生命周期模式的服务实例在释放上会有不同的策略。根据策略的释放特点可以归纳为如下两类。
Singleton:由于Singleton模式的服务实例是保存在根容器中,所以必须等到根容器对象被释放之后,Singleton模式的服务实例才会进行释放。
Scoped和Transient:由于这两种模式可以由根容器或子容器所创建,所以这两种模式的服务实例释放时机取决于创建它们的容器。如果创建它们的是根容器,则根容器释放时它们才会释放。如果创建它们的是子容器,那么子容器释放时它们才会释放。
存储列表
依赖注入框架中的容器在对服务进行存储时,会将其划分为两类:一类是用于存放已实例化服务的Realized Services列表,另一类是用于存放可释放服务实例的Disposable Services列表。
Realized Services列表在对Singleton和Scoped服务实例的提供上具有复用性,Singleton和Scoped提供时,首先会判断所属容器的Realized Services列表中是否存在对应的服务实例,如果Realized Services列表中存在对应的服务实例,那么就使用现有的服务实例进行提供。
如果Realized Services列表中不存在对应的服务实例,那么容器则会创建新的服务实例,并在提供之前先将新的服务实例添加到所属容器的Realized Services列表中。如果这个新创建的服务实例实现了IDisposable接口,那么这个新创建的服务实例还会被添加到所属容器的Disposable Services列表中。
对于Disposable Services列表需要强调的是,其中的存储的服务并不是已经被释放的,而是仅针对些实现了IDisposable接口的服务进行了添加。当容器对象被释放的时候,它会从自身的Disposable Services列表中提取出这些服务,并调用它们的Dispose方法,以便作为垃圾对象待GC回收。