開發者使用依賴注入時可能意識到服務實例的生命週期,但很多人並未完全掌握它是如何工作的。你可以在网上找到許多文章來解釋這些概念,但它們經常只是重複你可能已經知道的定義。讓我用手繪圖例來簡化解釋。
當實現依賴注入時,開發者有三個選擇決定實例的生命週期:
- 單例
- 範圍
- 暫存
雖然大部分開發者認識這些詞彙,但有不少人在為服務生命週期選擇哪個選項上有困難。
定義
讓我從定義開始:
- 單例生命週期的服務實例是從服務容器中一次創建一個應用程序。一個實例將服务于所有隨後的請求。單例服務在應用程序結束時 (即,在應用程序重新啟動時) 被消毀。
- 暫存生命週期的服務實例是從服務容器中每個請求創建的。暂存服務在請求結束時被消毀。
- 範圍生命週期的服務實例是每次客戶請求一次從服務容器中創建。暂存服務在請求結束時被消毀。
使用時機
- 單例 – 當你希望在應用程式的整個生命週期內使用服務的單一實例時
- 短暫的 – 當你希望在每个客戶端請求內使用服務的單一實例時
- 範圍 – 當你希望為每個請求使用服務的單一實例時
什麼是客戶端請求?用非常簡單的話來說,你可以將其視為一個API/REST呼叫,用戶通過按鈕點擊將其傳送到你的應用程式以獲得響應。
別擔心,讓我們通過一個例子來理解。
例子
首先,讓我們創建接口/服務和類別:
// 我們在下面宣告了3個服務
Public interface ISingleton
Public interface ITransient
Public interface IScoped
現在讓我們為每個服務接口/服務寫实的現行。我們將通過嘗試更新callMeSingleton
,callMeTransient
和callMeScoped
變數來理解這個概念。
- 單例類別實現:
class SingletonImplementation: ISingleton
{
var callMeSingleton = ""
// 其他實現
public SetSingleton(string value)
{
callMeSingleton = value;
}
// 其他實現
}
- 短暫類別實現:
class TransientImplementation: ITransient
{
var callMeTransient = ""
// 其他實現
public SetTransient(string value)
{
callMeTransient = value;
}
// 其他實現
}
- 範圍類別實現:
class ScopedImplementation: IScoped
{
var callMeScoped = ""
//其他實現
public SetScoped(string value)
{
callMeScoped = value;
}
//其他實現
}
讓我們用DI(依賴注入)來註冊(ConfigureServices
)以決定每個服務實例的生命週期:
services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();
我們將從三個不同的類別(ClassA
、ClassB
和 ClassC
)呼叫這些服務以了解每個服務的生命週期:
ClassA
:
public class ClassA
{
private ISingleton _singleton;
// constructor to instantiate 3 different services we creates
public ClassA(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassA()
{
_singleton.SetSingleton("I am from ClassA");
}
public void UpdateTransientFromClassA()
{
_transient.SetTransient("I am from ClassA");
}
public void UpdateScopedFromClassA()
{
_scoped.SetScoped("I am from ClassA");
}
// other implementation
}
ClassB
:
public class ClassB
{
private ISingleton _singleton;
// constructor to instantiate 3 different services we creates
public ClassB(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassB()
{
_singleton.SetSingleton("I am from ClassB");
}
public void UpdateTransientFromClassB()
{
_transient.SetTransient("I am from ClassB");
}
public void UpdateScopedFromClassB()
{
_scoped.SetScoped("I am from ClassB");
}
// other implementation
}
ClassC
:
public class ClassC
{
private ISingleton _singleton;
// constructor to instantiate 3 different services we creates
public ClassC(ISingleton singleton,
ITransient _transient,
IScoped _scoped)
{
_singleton = singleton;
}
public void UpdateSingletonFromClassC()
{
_singleton.SetSingleton("I am from ClassC");
}
public void UpdateTransientFromClassC()
{
_transient.SetTransient("I am from ClassC");
}
public void UpdateScopedFromClassC()
{
_scoped.SetScoped("I am from ClassC");
}
// other implementation
}
析論
讓我們分析上面實作中每個生命週期的結果和行為:
Singleton
所有類別(ClassA
、ClassB
和 ClassC
)在應用程序的生命週期內都会使用 SingletonImplementation
類的同一單一實例。這意味著 SingletonImplementation
類的屬性、字段和操作將被所有呼叫類使用的實例共享。對屬性或字段的任何更新將覆蓋先前的更改。
例如,在上述代碼中,`ClassA
`、`ClassB
` 和 `ClassC
` 都使用了 `SingletonImplementation
` 服務作為單例實例,並調用 `SetSingleton
` 來更新 `callMeSingleton
` 變量。在這種情況下,所有嘗試访问此屬性的請求將共享 `callMeSingleton
` 變量的單一值。無論哪個類最後訪問它來更新,都將覆蓋 `callMeSingleton
` 的值。`
ClassA
` – 對於 `TransientImplementation
` 服務,它將與其他類共享相同的實例。`ClassB
` – 對於 `TransientImplementation
` 服務,它將與其他類共享相同的實例。`ClassC
` – 對於 `TransientImplementation
` 服務,它將與其他類共享相同的實例。`
ClassA
`、`ClassB
` 和 `ClassC
` 正在更新 `SingletonImplementation
` 類的同一實例,這將覆蓋 `callMeSingleton
` 的值。因此,在設定或更新單例服務實現中的屬性時要小心。`
` 單例服務在應用程序結束時被處置(即在應用程序重新啟動時)。`
Transient`
所有類別 (`ClassA`、`ClassB` 和 `ClassC`) 將使用它們各自的 `TransientImplementation` 類別實例。這意味著如果一個類別呼叫 `TransientImplementation` 類別的屬性、字段或操作,它只会更新或覆寫其個別實例值。任何對屬性或字段的更新都不在其他 `TransientImplementation` 實例之間共享。
讓我們來了解:
ClassA
– 它將有自己的 `TransientImplementation` 服務實例。ClassB
– 它將有自己的 `TransientImplementation` 服務實例。ClassC
– 它將有自己的 `TransientImplementation` 服務實例。
假設您有一個 `ClassD`,它正在從 `ClassA`、`ClassB` 和 `ClassC` 實例呼叫瞬態服務。在這種情況下,每個類別實例將被當作不同的/單獨的實例處理,每個類別將有自己的 `callMeTransient` 值。閱讀下面的內嵌註釋以了解 `ClassD`:
public ClassD
{
// 其他實作
// 以下程式碼會將 callMeTransient 的值更新為 "I am from ClassA" ,僅僅對 ClassA 的實例產生影響。
// 而且它不會被 Class B 或 B 類的任何後續呼叫所更改。
ClassA.UpdateTransientFromClassA();
// 以下程式碼會將 callMeTransient 的值更新為 "I am from ClassB" ,僅僅對 ClassB 的實例產生影響。
// 它不會覆寫 ClassA 實例的值,也不会被 Class C 的後續呼叫所更改。
ClassB.UpdateTransientFromClassB();
// 以下程式碼會將 callMeTransient 的值更新為 "I am from ClassC" ,僅僅對 ClassC 的實例產生影響。
// 它不會覆寫 ClassA 和 ClassB 實例的值,也不会被其他類的後續呼叫所更改。
ClassC.UpdateTransientFromClassC();
// 其他實作
}
Transient 服務在每個請求結束時被釋放。當您希望在請求內達到無狀態行為時,請使用 Transient。
Scoped
所有類(ClassA
、ClassB
和 ClassC
)對於每個請求都会使用 ScopedImplementation
類的單一實例。這意味著在 ScopedImplementation
類上對屬性/字段/操作的調用將發生在請求範圍內的一個實例上。屬性/字段的任何更新都將影響其他類。
讓我們來了解:
ClassA
– 它將具有其TransientImplementation
服務的實例。ClassB
– 它將具有與ClassA
相同的TransientImplementation
服務實例。ClassC
– 它將具有與ClassA
和ClassB
相同的TransientImplementation
服務實例。
假設您有一個ClassD
,它從ClassA
、ClassB
和ClassC
實例中調用範圍內的服務。在這種情況下,每個類都將具有ScopedImplementation
類的单個實例。以下是對ClassD
的內聯注釋進行閱讀。
public class ClassD
{
// 其他實作
// 下列代碼將將callMeScoped的值更新為"我來自ClassA"對於ClassA實例
// 但由於它是一個範圍生命週期,所以它持有單個實例ScopedImplementation
// 然後它可以被ClassB或ClassC下一次呼叫覆寫
ClassA.UpdateScopedFromClassA();
// 下列代碼將將callMeScoped的值更新為對於單個實例ScopedImplementation"我來自ClassB"
// 它將也覆寫對於classA實例的callMeScoped值。
ClassB.UpdateScopedFromClassB();
// 現在如果Class A將對ScopedImplementation執行任何操作,
// 它將使用最新覆寫classB的屬性/字段值。
// 下列代碼將將callMeScoped的值更新為"我來自ClassC"
// 它將也覆寫對於classA和ClassB實例的callMeScoped值。
ClassC.UpdateScopedFromClassC();
// 現在如果Class B或Class A將對ScopedImplementation執行任何操作,它將使用最新覆寫classC的屬性/字段值。
// 其他實作
}
範圍服務在每個請求結束時被釋放。當你希望在單個請求之間的無狀態行為時使用Scoped。
小知識時間
服務的生命週期可以被父服務覆寫,在它被初始化時。混淆了嗎?讓我解釋:
讓我們取材於上方示例中的同一堂課,並從 SingletonImplementation
(這是一個單例)初始化 Transient 和 Scoped 服務。這將初始化 ITransient
和 IScoped
服務,並將這些服務的生命週期覆寫為單例生命週期作為父服務。在這種情況下,您的應用程序將不會有任何 Transient 或 Scoped 服務(假設您只使用我們在示例中使用的這3種服務)。
閱讀下面的代碼行:
public class SingletonImplementation: ISingleton
{
// 構造函數以添加初始化服務。
private readonly ITransient _transient
private readonly IScoped _scoped
SingletonImplementation(ITransient transient, IScoped scoped)
{
_transient = transient;
// 現在 _transient 將表现为單例服務,無論它是如何作為 Transient 註冊的
_scoped = scoped;
// 現在 scoped 將表现为單例服務,無論它是否作為 Scoped 註冊
}
var callMeSingleton = ""
// 其他實現
}
總結
我希望上述文章對理解該主題有幫助。我建議您在上述上下文中自己試一試,您將永遠不再混亂。單例是最容易理解的,因為一旦您創建了其實例,它將會在整個應用程序的 應用程序生命週期 內共享。與單例相似,Scoped 實例模擬相同行為,但僅限於應用程序內的一個請求生命週期內。Transient 完全無狀態,對於每個請求和每個類實例將保留其自己的服務實例。
Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle