跳到主要內容

[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));
}
}
}










本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計



※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務



※Google地圖已可更新顯示潭子電動車充電站設置地點!!



※帶您來看台北網站建置台北網頁設計,各種案例分享



Orignal From: [ASP.NET Core 3框架揭秘] 依賴注入[9]:實現概述

留言

這個網誌中的熱門文章

Python 併發總結,多線程,多進程,異步IO

1 測量函數運行時間 import time def profile(func): def wrapper(*args, ** kwargs): import time start = time.time() func( *args, ** kwargs) end = time.time() print ' COST: {} ' .format(end - start) return wrapper @profile def fib(n): if n<= 2 : return 1 return fib(n-1) + fib(n-2 ) fib( 35 )   2 啟動多個線程,並等待完成   2.1 使用threading.enumerate() import threading for i in range(2 ): t = threading.Thread(target=fib, args=(35 ,)) t.start() main_thread = threading.currentThread() for t in threading.enumerate(): if t is main_thread: continue t.join()   2.2 先保存啟動的線程 threads = [] for i in range(5 ): t = Thread(target=foo, args= (i,)) threads.append(t) t.start() for t in threads: t.join()   3 使用信號量,限制同時能有幾個線程訪問臨界區 from threading import Semaphore import time sema = Semaphor...

高雄十大包子名店出爐

, 圖文:吳恩文 高雄包子大賽落幕了,我只能就我個人意見, 介紹一下前十名這些包子,但是不能代表其他四位評審的意見,雖然身為評審長,我通常不會第一個表示意見,以免影響其他評審, 我主要工作是負責發問。   這次參賽的素包子很少,而且都不夠細致,又偏油,我不愛, 但是第一名的甜芝麻包-熔岩黑金包,竟然是素食得名- 漢來蔬食巨蛋店。   這包子賣相太好,竹炭粉的黑色外皮刷上金粉,一上桌,眾人驚呼, 搶拍照,內餡是芝麻餡,混一點花生醬增稠,加入白糖芝麻油, 熔岩爆漿的程度剛剛好,我一直以為芝麻要配豬油才行、 但是選到好的黑芝麻油一樣不減香醇, 當下有二位評審就想宅配回家。   尤其特別的是,黑芝麻餡室溫易化,師傅必須要輪班躲在冷藏室內, 穿著大外套才能包,一天包不了多少,我笑說,漢來美食,集團餐廳那麼多,實力雄厚,根本是「 奧運選手報名參加村裡運動會」嘛,其他都是小包子店啊, 但是沒辦法,顯然大家都覺得它好看又好吃, 目前限定漢來蔬食高雄巨蛋店,二顆88元,可以冷凍宅配, 但是要排一陣子,因為供不應求,聽說,四月份, 台北sogo店開始會賣。   第二名的包子,左營寬來順早餐店,顯然平易近人的多,一顆肉包, 十塊錢,是所有參賽者中最便宜的,當然,個頭也小, 它的包子皮明顯和其他不同,灰灰的老麵,薄但紮實有嚼勁, 肉餡新鮮帶汁,因為打了些水,味道極其簡單,就是蔥薑,塩, 香油,薑味尤其明顯,是老眷村的味道, 而特別的是老闆娘是台灣本省人, 當年完全是依據眷村老兵的口味一步一步調整而來,沒有加什麼糖、 五香粉,胡椒粉,油蔥酥。就是蔥薑豬肉和老麵香,能得名, 應該是它的平實無華,鮮美簡單,打動人心。   這是標準的心靈美食,可以撫慰人心,得名之前,寛來順已經天天排隊,現在,恐怕要排更久了, 建議大家六七點早點上門。   第三名,「專十一」很神奇,我記得比賽最後, 大家連吃了幾家不能引起共鳴的包子,有些累,到了專十一, 就坐著等包子,其他評審一吃,就催我趕快試,我一吃, 也醒了大半。   它的包子皮厚薄適中,但是高筋麵粉高些,老麵加一點點酵母, 我心中,它的皮屬一屬二,至於餡又多又好吃,蛋黃還是切丁拌入, 不是整顆放,吃起來「美味、均衡、飽滿」。一顆二十元。   老闆是陸軍專科十一期畢業取名專十一,...

韋伯連續劇終於更新 期待第一季順利完結

  地球天文學界的跳票大王詹姆斯·韋伯空間望遠鏡 (James Webb Space Telescope,縮寫為 JWST)自 1996 年以來斷斷續續不按劇本演出的連續劇終於讓焦慮的觀眾們又等到了一次更新:五層遮陽罩測試順利完成。 裝配完成的韋伯望遠鏡與好夥伴遮陽罩同框啦。Credit: NASA   嚴格的測試是任何空間任務順利成功的重中之重。遮陽罩,這個韋伯望遠鏡異常重要的親密夥伴,要是無法正常運轉的話,韋伯的這一季天文界連續劇說不準就要一直拖更了。   詹姆斯·韋伯空間望遠鏡是歷史上造出的最先進的空間望遠鏡。它不僅是一架紅外望遠鏡,還具有特別高的靈敏度。但想要達到辣么高的靈敏度來研究系外行星和遙遠的宇宙童年,韋伯童鞋必須非常"冷靜",體溫升高的話,靈敏度會大大折損。這個時候,遮陽罩就要大顯身手啦。   遮陽罩在韋伯的設計中至關重要。韋伯望遠鏡會被發射到拉格朗日 L2 點,運行軌道很高,遠離太陽、地球與月球。太陽是韋伯的主要熱量干擾的來源,其次是地球與月球。遮陽罩會有效阻斷來自這三大熱源的能量並保護韋伯維持在工作溫度正常運轉。這個工作溫度指的是零下 220 攝氏度(-370 華氏度;50 開爾文)。 上圖中我們可以看出,韋伯望遠鏡的配置大致可分為兩部分:紅色較熱的一面溫度為 85 攝氏度,藍色較冷的一面溫度達到零下 233 攝氏度。紅色的這部分中,儀器包括太陽能板、通信設備、計算機、以及轉向裝置。藍色部分的主要裝置包括鏡面、探測器、濾光片等。Credit: STSci.   遮陽罩的那一部分和望遠鏡的鏡面這部分可以產生非常極端的溫差。遮陽的這面溫度可以達到 110 攝氏度,足以煮熟雞蛋,而背陰處的部分溫度極低,足以凍結氧氣。   工程師們剛剛完成了五層遮陽罩的測試,按照韋伯在 L2 時的運行狀態安裝了遮陽罩。L2 距離地球約 160 萬公里。NASA 表示這些測試使用了航天器的自帶系統來展開遮陽罩,測試目前都已成功完成。韋伯望遠鏡遮陽罩負責人 James Cooper 介紹說這是遮陽罩"第一次在望遠鏡系統的电子設備的控制下展開。儘管這個任務非常艱巨,難度高,但測試順利完成,遮陽罩展開時的狀態非常驚艷"。   遮陽罩由五層 Kapton 製成。Kapton 是一種聚酰亞胺薄膜材料, 耐高溫絕...