ASP.NET Core中的依賴注入(5): ServiceProvider實現揭祕 【總體設計 】

發表於2016-05-08

 

本系列前面的文章我們主要以程式設計的角度對ASP.NET Core的依賴注入系統進行了詳細的介紹,如果讀者朋友們對這些內容具有深刻的理解,我相信你們已經可以正確是使用這些與依賴注入相關的API了。如果你還對這個依賴注入系統底層的實現原理具有好奇心,可以繼續閱讀這一節的內容。

目錄
一、ServiceCallSite

二、Service

三、ServiceEntry

四、ServiceTable

五、ServiceProvider

 

作為DI容器的體現,ServiceProvider是ASP.NET Core依賴注入系統的一個核心物件,但是預設的實現者是一個定義在程式集 “Microsoft.Extensions.DependencyInjection.dll” 中的一個名為 “ServiceProvider” 內部(Internal)型別,而且它所依賴的很多介面和型別也是如此,所以我相信實現在這個ServiceProvider類中的服務提供機制對於絕大部分人是陌生的。本節提及的ServiceProvider不是泛指實現了IServiceProvider介面的型別,而是專指ServiceProvider這個內部型別。

為了讓讀者朋友們能夠深刻地瞭解ServiceProvider內部的實現原理,我會在本節內容中重新定義它。在這裡需要特別說明的是我們重建的ServiceProvider以及其他重建的介面和類旨在體現真實ServiceProvider設計思想和實現原理,在具體的原始碼層面是有差異的。考慮到篇幅的問題,很多細節的內容將不會體現在我們重建的介面和型別中。如果想了解原始的實現邏輯,可以從GitHub上下載原始碼。

從總體設計的角度來審視ServiceProvider,需要涉及與之相關的4個核心物件,包括ServiceCallSite、Service、ServiceEntry和ServiceTable,它們均體現為相應的介面和類,並且這些介面和淚都是內部的,接下來我們就來逐一認識它們。

一、ServiceCallSite

ServiceProvider的核心功能就是針對服務型別提供相應的服務例項,而服務例項的提供最終是通過ServiceCallSite來完成的。ServiceCallSite體現為具有如下定義的IServiceCallSite介面,除了直接提供服務例項的Invoke方法之外,它還具有另一個返回型別為Expression的Build方法,該方法將定義在Invoke方法中的邏輯定義成一個表示式。

在真正提供服務例項的時候,ServiceProvider在收到針對某個服務型別的第一個服務獲取請求時,他會直接呼叫對應ServiceCallSite的Invoke方法返回提供的服務例項。與此同時,這個ServiceCallSite的Build方法會被呼叫並生成一個表示式,該表示式進一步編譯成一個型別為Func的委託物件並被快取起來。針對同一個服務型別的後續服務例項將直接使用這個快取的委託物件來提供。

二、Service

我們知道ServiceProvider提供服務的依據來源於建立它指定一個ServiceCollection物件,用於指導ServiceProvider如何提供所需服務的資訊以ServiceDescriptor物件的形式儲存在這個集合物件中。當ServiceProvider被初始化後,每一個ServiceDescriptor將會被轉換成一個Service物件,後者體現為如下一個IService介面。

Service的Lifetime屬性自然來源於ServiceDescriptor的同名屬性,它的CreateCallSite方法返回一個針對用於提供對應服務例項的ServiceCallSite物件。由於Service物件可以建立ServiceCallSite,所以它自然具有提供服務例項的能力。Service總是作為連結串列的某個節點存在,這個連結串列是具有相同服務型別(對應ServiceType屬性)的多個ServiceDescriptot生成的,Service的Next屬性保持著對連結串列後一個節點的引用。

三、ServiceEntry

上面我們所說的由Service物件組成的連結串列體現為如下一個ServiceEntry類。我們為ServiceEntry定義了三個屬性(First、Last、All)分別代筆這個連結串列的第一個節點、最後一個節點以及所有節點,節點型別為IService。如果需要在鏈尾追加一個Service物件,可以直接呼叫Add方法。

四、ServiceTable

多個ServiceEntry組成一個ServiceTable。如下面的程式碼片段所示,一個ServiceTable通過其只讀屬性ServieEntries維護著一組ServiceEntry物件與它們對應的服務型別之間的對映關係。一個ServiceTable物件通過一個ServiceCollection物件建立出來。如下面的程式碼片段所示,組成ServiceCollection的所有ServiceDescriptor物件先根據其ServiceType屬性體現的服務型別進行分組,由每組ServiceDescriptor建立的ServiceEntry物件與對應的服務型別之間的對映會被新增到ServiceEntries屬性中。

從上面的程式碼片段可以看出組成ServiceEntry的是一個型別為Service的物件,該型別定義如下。Service類實現了IService介面並通過一個ServiceDescriptor物件建立而成。我們省略了定義在方法CreateCallSite中建立ServiceCallSite的邏輯,後續在介紹各種型別的ServiceCallSite的時候我們會回來講述該方法的實現。

五、ServiceProvider

如下所示的程式碼片段揭示了實現在ServiceProvider之中與服務提供和回收相關的基本實現原理。我們先來簡單介紹定義在它內部的幾個屬性。Root屬性返回的ServiceProvider代表它的根,對於一個獨立的ServiceProvider來說,這個根就是它自己。ServiceTable屬性返回根據ServiceCollection建立的ServiceTable物件。上面介紹ServiceCallSite的時候,我們提到它的Build方法返回的表示式會編譯成一個型別為Func 的委託,並被快取起來服務於後續針對同一個型別的服務提供請求,該委託物件與對應服務型別之間的對映關係就儲存在RealizedServices屬性中。

對於採用Scoped模式提供的服務例項,ServiceProvider需要自行對它們進行維護,具體來說它們會和對應的Service物件之間的對映關係會儲存在ResolvedServices屬性中。如果採用Transient模式,對於提供過的服務例項,如果自身型別實現了IDisposble介面,它們會被新增到TransientDisposableServices屬性返回的列表中。當Dispose方法執行的時候,這兩組物件的Dispose方法會被執行。

真正的服務提供機制體現在ServiceProvider實現的GetService方法中,實現邏輯其實很簡單:ServiceProvider會根據指定的服務型別從RealizedServices屬性中查詢是否有通過編譯表示式生成的Func委託生成出來,如果存在則直接使用它生成提供的服務例項。如果這樣的委託不存在,則會試著從ServiceTable中找到對應的ServiceEntry,如果不存在直接返回Null,否則會呼叫ServiceEntry所在列表最後一個Service的CreateServiceCallSite方法建立一個ServiceCallSite物件(這一點說明了如果針對同一個服務型別註冊了多個ServiceDescriptor,在提供單個服務的時候總是使用最後一個ServiceDescriptor)。

接下來這個ServiceCallSite的Invoke方法被呼叫來建立服務例項,在返回該例項之前它的Build方法會被呼叫,返回的表示式被編譯成Func委託並被新增到RealizedServices屬性中。如果ServiceProvider後續需要提供同型別的服務,這個委託物件將被啟用。

 

相關文章