1. 程式人生 > >依賴注入在 dotnet core 中實現與使用:1 基本概念

依賴注入在 dotnet core 中實現與使用:1 基本概念

關於 Microsoft Extension: DependencyInjection 的介紹已經很多,但是多數偏重於實現原理和一些特定的實現場景。作為 dotnet core 的核心基石,這裡準備全面介紹它的概念、原理和使用。

這裡首先介紹概念部分。

1. 概念

該專案在 GitHub 的地址:https://github.com/aspnet/Extensions/tree/master/src/DependencyInjection

Microsoft.Extensions.DependencyInjection 是微軟對依賴倒置原則的實現。作為 ASP.NET Core 的基石,DependencyInjection

貫穿了整個專案的方方面面,掌握它的使用方式和原理,不僅對理解 ASP.NET Core 有重要意義,也有助於將它運用到其它專案的開發中,幫助提供專案開發的效率和質量。

1.1 問題的場景

在軟體開發中,專案通常有多個不同的模組組成,模組之間存在依賴關係。例如,我們考慮一個簡化的場景,我們有 3 個關於使用者的類:

  1. AccountController,提供使用者互動介面

  2. UserService,提供使用者管理的業務邏輯

  3. UserRepository,提供使用者管理的資料訪問

AccountController 內部需要使用 UserService 的例項 來管理使用者,而 UserService

內部則需要基於 UserRepository 來提供資料訪問。我們稱它們之間存在依賴關係。或者表達為,AccountController 依賴於 UserService ,而 UserService 依賴於 UserRepository 。而依賴注入就是用來幫助我們實現依賴管理的有力工具。

1.2 依賴倒置原則 DIP

依賴倒置原則是廣為人知的設計原則之一,該原則是實現軟體專案中模組的解耦的理論基石。

原則的定義如下:

High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.

翻譯過來為:

  • 高層模組不應該依賴低層模組,兩者都應該依賴抽象

  • 抽象不應該依賴細節

  • 細節應該依賴抽象

在沒有實現依賴倒置原則的時候,我們通過在 AccountController 類中自己通過 new 關鍵字來建立其依賴的 UserService 物件例項,

public class AccountController {
​
    private readonly UserService _userService;
    public AccountController() {
        this._userService = new UserService();
    }
}

 

這導致了兩個類之間的緊耦合,AccountControllerUserService 被繫結到一起, 在每次建立 AccountController 的時候,一定會建立一個 UserService 的物件例項,而如果我們需要測試 AccountController 的時候,也就不得不考慮 UserService,這樣一級一級的依賴下來,UserService 又會依賴 UserRepository,就會發現專案中的類都被繫結在一起, 緊密耦合,難以分拆。

基於依賴倒置的原則,通常會考慮通過介面進行隔離。例如,我們可能會定義一個使用者服務的介面:

public interface IUserService
{  
}

 

而使用者服務則會實現該介面

public class UserService : IUserService {
}

 

AccountController 類中,則改變成了基於介面來使用 UserService

public class AccountController {
​
    private readonly IUserService _userService;
    public AccountController() {
        this._userService = new UserService();
    }
}

 

雖然在 HomeController 內部,我們可以基於介面程式設計了,但是這樣的作法並沒有解決自己通過 new 來獲取 UserService 物件例項的問題。

1.3 控制反轉 IoC

IoC是一種著名的實現 DIP 的設計模式。

它的核心思想是:在需要物件例項的時候,不要總考慮自己通過 new 來建立物件,放下依賴物件的建立過程,而是把建立物件的工作交給別人來負責,這個別人我們通常稱為 容器 (Container) 或者 服務提供者 (ServiceProvider), 我們後面使用這個 ServiceProvider 來指代它,

在需要物件例項的時候,從這個 ServiceProvider 中獲取。

下面是一個廣泛使用的示意圖。拿總是要拿的,但是從 自己穿上 變成了 給你穿上

在控制反轉中,引入了一個 ServiceProvider 來幫助我們獲得物件例項。

 

1.4 依賴注入 DI (DependencyInjection)

DI 是 IoC 模式的一種實現。

《Expert one on one J2EE Development without EJB》第 6 章

IoC 的主要實現方式有兩種:依賴查詢,依賴注入 (p128)

依賴注入是一種更可取的方式。(p130)

Martin Fowler 的原文:

As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.

大意是:

已經存在某種模式,該模式被稱為 IoC,但 IoC 太過廣義,任何框架都 IoC,為了讓表意更明確,決定採用 DI 來精確指稱它。

DI 的實現有多種,我們這裡介紹的是微軟官方在 Microsoft Extension 中內建提供的 DependencyInjection。它是 IoC 中一種實現,ASP.NET Core 的整個核心基於它來實現。同時,我們也可以在其它專案中使用,以實現對依賴倒置原則的支援。

2. DependencyInjection 中的基本概念

2.1 服務描述集合 ServiceCollection

在微軟的 DI 實現中,所有的服務需要首先註冊到一個公共的服務描述集合中,該集合對於整個 DI 來說,只需要一個,服務只需要在此集合中註冊一次,即可在以後通過 DI 提供給使用者。

該集合的介面定義為 IServiceCollection,可以看出來,它其實就是一個用來儲存服務註冊的集合。

public interface IServiceCollection : IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
{
}

 

系統預設已經實現了一個對 IServiceCollection 的實現,名為 ServiceCollection。在 ASP.NET Core 中,內部會建立該物件的例項,我們也可以在其它專案中,自己來建立它,很簡單,直接 new 出來就可以使用了。

IServiceCollection services = new ServiceCollection ();

 在 ASP.NET Core MVC 中,你可能已經見過它了,不需要你來建立,系統已經幫你做了。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IRepository, MemoryRepository>();
    services.AddMvc();
}

 

2.2 服務 Service

在 DI 語境中,服務特指通過 DI 容器管理的物件例項。這個服務並不一定被稱為 **Service,而是可以是任何由 DI 所管理的物件,只是在 DI 這個語境下,我們將其統稱為服務。

服務是我們自己定義的,例如前面提到的 AccountControllerUserService 等等。

我們通過 DI 來獲得服務物件例項,管理服務物件的生命週期,對於存在複雜依賴關係的物件, DI 還負責管理這些例項之間的依賴關係。

服務必須首先註冊在 DI 中才能使用,但是,註冊前需要首先考慮和決定服務的生命週期。

2.3 服務的生命週期

服務物件例項有著不同型別的生命週期。有些物件的生命週期與應用程式相同,在應用程式啟動時建立,在應用程式退出時才需要釋放。例如我們的資料訪問物件例項。有些物件僅僅在當前方法中使用,在方法呼叫結束之後就應該銷燬。服務的生命週期管理用來管理這些需求。

DI 支援三種類型的生命週期:

  1. Singleton,單例,在當前應用程式環境下只有一個例項。例如資料訪問服務物件例項。

  2. Scoped,限定範圍,一旦退出此範圍,在此範圍內的服務物件都需要銷燬。例如 Web 開發中的請求物件例項。

  3. Transient,瞬態,一次性使用,每次從 DI 中獲取,都返回一個新的例項。

Microsoft.Extensions.DependencyInjection.ServiceLifetime

public enum ServiceLifetime
{
    Singleton,
    Scoped,
    Transient
}

 

服務的生命週期在註冊服務的時候確定。在使用的時候,直接獲取例項,不再指定服務的生命週期。微軟提供了多種擴充套件方法來便於在註冊服務時指定服務的生命週期。例如下面是通過泛型方式來指定單例模式的生命週期。

// 基於介面的註冊
services.AddSingleton<IUserService, UserService>();

 

2.4 服務提供者 ServiceProvider

在需要使用服務物件例項的時候,不是從註冊服務的集合中獲取,而是需要通過服務提供者來獲取,這個服務提供者顯然需要來自注冊服務的集合。服務提供者的介面定義為 IServiceProvider,它是 .net 的基礎定義之一,不是在該 DI 框架中定義的。

public interface IServiceProvider
{
    object GetService(Type serviceType);
}

 

DI 中的 ServiceCollectionContainerBuilderExtensions 擴充套件了 IServiceCollection,提供了獲得這個服務提供者 ServiceProvider 的支援。

public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
{
    return BuildServiceProvider(services, ServiceProviderOptions.Default);
}

 

所以,我們通常使用該方法來獲取並使用它。

// 建立註冊服務的容器
IServiceCollection services = new ServiceCollection ();
// 註冊服務,這裡指定了單例
services.AddSingleton<IUserService, UserService>();
// 通過容器獲得服務提供者
IServiceProvider provider = services.BuildServiceProvider ();

 

2.5 獲取服務物件例項

通過服務提供者來手動獲取服務物件例項。通過註冊的服務型別,直接呼叫 GetService 方法即可。

例如,前面我們註冊了服務型別 IUserService 的實現型別是 UserService ,那麼,可以通過此型別來獲取實際實現該介面的物件例項。

// 建立註冊服務的容器
IServiceCollection services = new ServiceCollection ();
// 註冊服務,這裡指定了單例
services.AddSingleton<IUserService, UserService>();
// 通過容器獲得服務提供者
IServiceProvider provider = services.BuildServiceProvider ();
// 通過介面獲取服務物件例項
IUserService instance = provider.GetService<IUserService> ();

 

看起來,更加複雜了。在實際使用中,我們很少使用這樣的方式來使用 DI,後面我們再深入討論具體的使用過程。

&n