在現實生活中,生命周期一詞往往代表著某些人或事物從生到死的過程,而在依賴註入框架中,生命周期中的“生與死”體現為服務實例的創建和釋放。實際上對於介紹依賴註入框架的生命周期而言,就是在介紹依賴註入容器採用什麼樣的方式創建和釋放服務實例。 多個容器之間的組織結構 在介紹生命周期之前,我們必須先對“多個容 ...
在現實生活中,生命周期一詞往往代表著某些人或事物從生到死的過程,而在依賴註入框架中,生命周期中的“生與死”體現為服務實例的創建和釋放。實際上對於介紹依賴註入框架的生命周期而言,就是在介紹依賴註入容器採用什麼樣的方式創建和釋放服務實例。
多個容器之間的組織結構
在介紹生命周期之前,我們必須先對“多個容器之間的組織結構”和“服務範圍”有一個基本的瞭解,因為某類生命周期模式的服務實例和“服務範圍”息息相關的,如果該模式的服務實例不在“服務範圍”進行使用,那麼就失去了使用該模式的意義。“你知道我說的是哪個模式嗎?”
在依賴註入框架中容器並不是作為一個單一化的結構存在。最開始會初始化一個根容器,我們可以利用根容器創建一個服務範圍,創建的服務範圍中就會包含一個容器,這種容器我們又稱為子容器。基於這種創建方式,我們必須清楚知道一點的是:除了根容器之外,所有的子容器都包含在一個服務範圍中。而對於每個服務範圍而言,它可以不斷向下延申創建更多服務範圍,從而獲得更多的子容器。以這種類似於“父子關係”不斷向下延申創建,最終從表示上來看多個容器的組織會形成一個樹形層次化的結構。
而對於上圖中這種樹形層次化的結構,僅僅是我們根據容器的創建形式主觀意識上的一種邏輯結構,並非是依賴註入框架中實際的容器層次結構。從各個容器對象的引用層面來看,服務範圍中的容器對象(子容器)並不需要知道自己的“父容器”是誰,它只在乎根容器對象在哪裡,所以每個子容器對象只會針對根容器對象進行引用,根據這種引用情況多個容器之間的組織結構如下圖所示。
在代碼中如果要創建一個子容器,首先需要獲取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回收。
知識改變命運