1. 程式人生 > >[ASP.NET Core 3框架揭祕] 依賴注入[8]:服務例項的生命週期

[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會直接建立一個新的服務例項。在作為最終的服務例項被返回之前,建立的服務例項會被新增到自身的Realized Services列表中,如果例項型別實現了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]:與第三方依賴注入框架的適配