[ASP.NET Core 3框架揭祕] 依賴注入[9]:實現概述
《服務註冊》、《服務消費》和《生命週期》主要從實現原理的角度對.NET Core的依賴注入框架進行了介紹,接下來更進一步,看看該框架的總體設計和實現。在過去的多個版本更迭過程中,依賴注入框架的底層實現一直都在發生改變,加上底層的涉及的大都是內容介面和型別,所以我們不打算涉及太過細節的層面。
一、ServiceProviderEngine & ServiceProviderEngineScope
對於依賴注入的底層設計和實現來說,ServiceProviderEngine和ServiceProviderEngineScope是兩個最為核心的型別。顧名思義,ServiceProviderEngine表示提供服務例項的提供引擎,服務例項最終是通過該引擎提供的,在一個應用範圍內只存在一個全域性唯一的ServiceProviderEngine物件。ServiceProviderEngineScope代表服務範圍,它利用對提供服務例項的快取實現對生命週期的控制。ServiceProviderEngine實現了介面IServiceProviderEngine,從如下的程式碼片段可以看出,一個ServiceProviderEngine物件同時也是一個IServiceProvider物件,還是一個IServiceScopeFactory物件。
internal interface IServiceProviderEngine : IServiceProvider, IDisposable, IAsyncDisposable { void ValidateService(ServiceDescriptor descriptor); IServiceScope RootScope { get; } } internal abstract class ServiceProviderEngine : IServiceProviderEngine, IServiceScopeFactory { public IServiceScope RootScope { get; } public IServiceScope CreateScope(); ... }
ServiceProviderEngine的RootScope屬性返回的IServiceScope物件是為根容器提供的服務範圍。作為一個IServiceScopeFactory物件,ServiceProviderEngine的CreateScope會建立一個新的服務範圍,這兩種服務範圍都通過一個ServiceProviderEngineScope物件來表示。
internal class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IAsyncDisposable { public ServiceProviderEngine Engine { get; } public IServiceProvider ServiceProvider { get; } public object GetService(Type serviceType); }
如上面的程式碼片段所示,一個ServiceProviderEngineScope物件不僅是一個IServiceScope物件,還是一個IServiceProvider物件。在《生命週期》中,我們說表示服務範圍的IServiceScope物件是對一個表示依賴注入容器的IServiceProvider物件的封裝,實際上兩者合併為同一個ServiceProviderEngineScope物件,一個ServiceProviderEngineScope物件的ServiceProvider屬性返回的就是它自己。換句話說,我們所謂的子容器和它所在的服務範圍引用的都是同一個ServiceProviderEngineScope物件。
下圖進一步揭示了ServiceProviderEngine和ServiceProviderEngineScope之間的關係。對於一個通過呼叫ServiceProviderEngine物件的CreateScope建立的ServiceProviderEngineScope來說,由於它同時也是一個IServiceProvider物件,如果我們呼叫它的GetService<IServiceProvider>方法,該方法同樣返回它自己。如果我們呼叫它的GetService<IServiceScopeFactory>方法,它返回建立它的ServiceProviderEngine物件,也就是該方法和Engine屬性返回同一個物件。
依賴注入框架提供的服務例項最終是通過ServiceProviderEngine物件提供的。從上面給出的程式碼片段可以看出,ServiceProviderEngine是一個抽象類,.NET Core依賴注入框架提供瞭如下四個具體的實現型別,預設使用的是DynamicServiceProviderEngine。
- RuntimeServiceProviderEngine:採用反射的方式提供服務例項;
- ILEmitServiceProviderEngine:採用IL Emit的方式提供服務例項;
- ExpressionsServiceProviderEngine:採用表示式樹的方式提供服務例項;
- DynamicServiceProviderEngine:根據請求併發數量動態決定最終的服務例項提供方案(反射和者IL Emit或者反射與表示式樹,是否選擇IL Emit取決於當前執行時是否支援Reflection Emit)。
4.4.2. ServiceProvider
呼叫IServiceCollection集合的擴充套件方法BuildServiceProvider建立的是一個ServiceProvider物件。作為根容器的ServiceProvider物件,和前面介紹的ServiceProviderEngine和ServiceProviderEngineScope物件,一起構建了整個依賴注入框架的設計藍圖。
在利用IServiceCollection集合建立ServiceProvider物件的時候,提供的服務註冊將用來建立一個具體的ServiceProviderEngine物件。該ServiceProviderEngine物件的RootScope就是它建立的一個ServiceProviderEngineScope物件,子容器提供的Singleton服務例項由它維護。如果呼叫ServiceProvider物件的GetService<IServiceProvider>方法,返回的其實不是它自己,而是作為RootScope的ServiceProviderEngineScope物件(呼叫ServiceProviderEngineScope物件的GetService<IServiceProvider>方法返回的是它自己)。
ServiceProvider和ServiceProviderEngineScope都實現了IServiceProvider介面,如果我們呼叫了它們的GetService<IServiceScopeFactory>方法,返回的都是同一個ServiceProviderEngine物件。這一個特性決定了呼叫它們的CreateScope擴充套件方法都會建立一個新的ServiceProviderEngineScope物件作為子容器。綜上所述,我們針對依賴注入框架總結出如下的特性:
- ServiceProviderEngine的唯一性:整個服務提供體系只存在一個唯一的ServiceProviderEngine物件。
- ServiceProviderEngine與IServiceFactory的同一性:唯一存在的ServiceProviderEngine會作為建立服務範圍的IServiceFactory工廠。
- ServiceProviderEngineScope和IServiceProvider的同一性:表示服務範圍的ServiceProviderEngineScope同時也是作為服務提供者的依賴注入容器。
為了印證我們總結出來的特性,我們編寫的測試程式碼。由於設計的ServiceProviderEngine和ServiceProviderEngineScope都是內部型別,我們只能採用反射的方式得到它們的屬性或者欄位成員。上面總結的這些特徵體現在如下幾組除錯斷言中。
class Program { static void Main() { var (engineType, engineScopeType) = ResolveTypes(); var root = new ServiceCollection().BuildServiceProvider(); var child1 = root.CreateScope().ServiceProvider; var child2 = root.CreateScope().ServiceProvider; var engine = GetEngine(root); var rootScope = GetRootScope(engine, engineType); //ServiceProviderEngine的唯一性 Debug.Assert(ReferenceEquals(GetEngine(rootScope, engineScopeType), engine)); Debug.Assert(ReferenceEquals(GetEngine(child1, engineScopeType), engine)); Debug.Assert(ReferenceEquals(GetEngine(child2, engineScopeType), engine)); //ServiceProviderEngine和IServiceScopeFactory的同一性 Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceScopeFactory>(), engine)); Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceScopeFactory>(), engine)); Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceScopeFactory>(), engine)); //ServiceProviderEngineScope提供的IServiceProvider是它自己 //ServiceProvider提供的IServiceProvider是RootScope Debug.Assert(ReferenceEquals(root.GetRequiredService<IServiceProvider>(), rootScope)); Debug.Assert(ReferenceEquals(child1.GetRequiredService<IServiceProvider>(), child1)); Debug.Assert(ReferenceEquals(child2.GetRequiredService<IServiceProvider>(), child2)); //ServiceProviderEngineScope和IServiceProvider的同一性 Debug.Assert(ReferenceEquals((rootScope).ServiceProvider, rootScope)); Debug.Assert(ReferenceEquals(((IServiceScope)child1).ServiceProvider, child1)); Debug.Assert(ReferenceEquals(((IServiceScope)child2).ServiceProvider, child2)); } static (Type Engine, Type EngineScope) ResolveTypes() { var assembly = typeof(ServiceProvider).Assembly; var engine = assembly.GetTypes().Single(it => it.Name == "IServiceProviderEngine"); var engineScope = assembly.GetTypes().Single(it => it.Name == "ServiceProviderEngineScope"); return (engine, engineScope); } static object GetEngine(ServiceProvider serviceProvider) { var field = typeof(ServiceProvider).GetField("_engine", BindingFlags.Instance | BindingFlags.NonPublic); return field.GetValue(serviceProvider); } static object GetEngine(object enginScope, Type engineScopeType) { var property = engineScopeType.GetProperty("Engine", BindingFlags.Instance | BindingFlags.Public); return property.GetValue(enginScope); } static IServiceScope GetRootScope(object engine, Type engineType) { var property = engineType.GetProperty("RootScope", BindingFlags.Instance | BindingFlags.Public); return (IServiceScope)property.GetValue(engine); } }
三、注入IServiceProvider物件
在《依賴注入模式》中,我們從“Service Locator”設計模式是反模式的角度說明了為什麼不推薦在服務中注入IServiceProvider物件。不過反模式並不就等於是完全不能用的模式,有些情況下直接在服務建構函式中注入作為依賴注入容器的IServiceProvider物件可能是最快捷省事的解決方案。對於IServiceProvider物件的注入,有個細節大家可能忽略或者誤解。
讀者朋友們可以試著思考這麼一個問題:如果我們在某個服務中注入了IServiceProvider物件,當我們利用某個IServiceProvider物件來提供該服務例項的時候,注入的IServiceProvider物件是它自己嗎?以如下所示的程式碼片段為例,我們定義了兩個在建構函式中注入了IServiceProvider物件的服務型別SingletonService和ScopedService,並按照命名所示的生命週期進行了註冊。
class Program { static void Main() { var serviceProvider = new ServiceCollection() .AddSingleton<SingletonService>() .AddScoped<ScopedService>() .BuildServiceProvider(); using (var scope = serviceProvider.CreateScope()) { var child = scope.ServiceProvider; var singletonService = child.GetRequiredService<SingletonService>(); var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, scopedService.RequestServices)); Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices)); } } public class SingletonService { public IServiceProvider ApplicationServices { get; } public SingletonService(IServiceProvider serviceProvider) => ApplicationServices = serviceProvider; } public class ScopedService { public IServiceProvider RequestServices { get; } public ScopedService(IServiceProvider serviceProvider) => RequestServices = serviceProvider; } }
我們最終利用一個作為子容器的IServiceProvider物件(ServiceProviderEngineScope物件)來提供這來個服務型別的例項,並通過除錯斷言確定注入的IServiceProvider物件是否就是作為當前依賴注入容器的ServiceProviderEngineScope物件。如果在Debug模式下執行上述的測試程式碼,我們會發現第一個斷言是成立的,第二個則不成立。
再次回到兩個服務型別的定義,SingletonService和ScopedService中通過注入IServiceProvider物件初始化的屬性分別被命名為ApplicationServices和RequestServices,意味著它們希望注入的分別是針對當前應用程式的根容器和針對請求的子容器。當我們利用針對請求的子容器來提供針對這兩個型別的服務例項時,如果注入的當前子容器的話,就與ApplicationServices的意圖不符。所以在提供服務例項的注入的IServiceProvider物件取決於採用的生命週期模式,具體策略為:
- Singleton:注入的是ServiceProviderEngine的RootScope屬性表示的ServiceProviderEngineScope物件。
- Scoped和Transient:如果當前IServiceProvider物件型別為ServiceProviderEngineScope,注入的就是它自己,如果是一個ServiceProvider物件,注入的還是ServiceProviderEngine的RootScope屬性表示的ServiceProviderEngineScope物件。
基於生命週期模式注入IServiceProvider物件的策略可以通過如下這個測試程式來驗證。最後還有一點需要補充一下:我們將呼叫IServiceCollection集合的BuildServiceProvider擴充套件方法建立的ServiceProvider物件作為根容器,它對應的ServiceProviderEngine物件的RootScope屬性返回作為根服務範圍的ServiceProviderEngineScope物件,ServiceProvider、ServiceProviderEngine和ServiceProviderEngineScope這三個型別全部實現了IServiceProvider介面,這三個物件都可以視為根容器。
class Program { static void Main() { var serviceProvider = new ServiceCollection() .AddSingleton<SingletonService>() .AddScoped<ScopedService>() .BuildServiceProvider(); var rootScope = serviceProvider.GetService<IServiceProvider>(); using (var scope = serviceProvider.CreateScope()) { var child = scope.ServiceProvider; var singletonService = child.GetRequiredService<SingletonService>(); var scopedService = child.GetRequiredService<ScopedService>(); Debug.Assert(ReferenceEquals(child, child.GetRequiredService<IServiceProvider>())); Debug.Assert(ReferenceEquals(child, scopedService.RequestServices)); Debug.Assert(ReferenceEquals(rootScope, singletonService.ApplicationServices)); } } }
[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]:與第三方依賴注入框架的