關於 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 個關於用戶的類:
-
AccountController,提供用戶交互界面
-
UserService,提供用戶管理的業務邏輯
-
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(); } }
這導致了兩個類之間的緊耦合,AccountController
與 UserService
被綁定到一起, 在每次創建 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 這個語境下,我們將其統稱為服務。
服務是我們自己定義的,例如前面提到的 AccountController
和 UserService
等等。
我們通過 DI 來獲得服務對象實例,管理服務對象的生命周期,對於存在復雜依賴關系的對象, DI 還負責管理這些實例之間的依賴關系。
服務必須首先注冊在 DI 中才能使用,但是,注冊前需要首先考慮和決定服務的生命周期。
2.3 服務的生命周期
服務對象實例有着不同類型的生命周期。有些對象的生命周期與應用程序相同,在應用程序啟動時創建,在應用程序退出時才需要釋放。例如我們的數據訪問對象實例。有些對象僅僅在當前方法中使用,在方法調用結束之后就應該銷毀。服務的生命周期管理用來管理這些需求。
DI 支持三種類型的生命周期:
-
Singleton,單例,在當前應用程序環境下只有一個實例。例如數據訪問服務對象實例。
-
Scoped,限定范圍,一旦退出此范圍,在此范圍內的服務對象都需要銷毀。例如 Web 開發中的請求對象實例。
-
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,后面我們再深入討論具體的使用過程。
2.6 構造函數注入
DI 支持構造函數注入。
定義 IUserRepository
接口,並實現 UserRepository
。
public interface IUserRepository { } public class UserReposotory: IUserRepository { }
UserService
通過構造函數依賴 IUserRepository
。
public class UserService : IUserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { this._userRepository = userRepository; } }
在通過 DI 獲得 UserService
實例的時候,DI 幫助實例化其所依賴的 UserRepository
實例。
在 UserService
IServiceCollection services = new ServiceCollection (); // 基於接口的注冊 services.AddSingleton<IUserService, UserService>(); services.AddSingleton<IUserRepository, UserReposotory>(); IServiceProvider provider = services.BuildServiceProvider (); IUserService instance = provider.GetService<IUserService> ();