[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地圖已可更新顯示潭子電動車充電站設置地點!!

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