探索 .NET Core 依賴注入的 IServiceProvider

SpringLeee發表於2021-03-01

在上一篇文章中,我們學習了Microsoft.Extensions.DependencyInjection中的IServiceCollection,包括服務註冊轉換為ServiceDescriptors,然後新增到集合中。

探索 .NET Core 依賴注入的 IServiceCollection

在本文中,我們會學習 IServiceProvider,瞭解它是什麼,以及它是怎麼建立出來的,我們將根據上一篇文章中建立的IServiceCollection來學習如何構建IServiceProvider。

什麼是 IServiceProvider?

IServiceProvider會根據程式的要求在執行時解析服務型別的例項,ServiceProvider來保證已解析的服務在預期的生命週期內有效,這個實現設計的非常高效,所以服務的解析速度非常快。

構建一個 IServiceProvider

首先,當我們把服務都新增到 IServiceCollection ,接下來會構建一個IServiceProvider, 它能夠提供我們程式中所依賴服務的例項,本質上它包裝了 IServiceCollection。

通過呼叫 BuildServiceProvider(IServiceCollection上的一個擴充套件方法)完成構建:

var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ClassA>();
serviceCollection.AddSingleton<IThing, ClassB>();
 
var serviceProvider = serviceCollection.BuildServiceProvider();

當我們沒有傳入任何引數時,它會建立一個 ServiceProviderOptions 的一個預設例項:

public static class ServiceCollectionContainerBuilderExtensions
{ 
	public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
	{
		return services.BuildServiceProvider(ServiceProviderOptions.Default);
	}

ServiceProviderOptions 有兩個屬性,在本文後邊的內容,我會詳細介紹這些:

public class ServiceProviderOptions
{
    public bool ValidateScopes { get; set; }
    public bool ValidateOnBuild { get; set; }
}

BuildServiceProvider 的方法內部是這樣的:

public static ServiceProvider BuildServiceProvider(this IServiceCollection services, 
    ServiceProviderOptions options)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }
    if (options == null)
    {
        throw new ArgumentNullException(nameof(options));
    }
    IServiceProviderEngine engine;
#if !NETCOREAPP
    engine = new DynamicServiceProviderEngine(services);
#else
    if (RuntimeFeature.IsDynamicCodeCompiled)
    {
        engine = new DynamicServiceProviderEngine(services);
    }
    else
    {
        // Don't try to compile Expressions/IL if they are going to get interpreted
        engine = new RuntimeServiceProviderEngine(services);
    }
#endif
    return new ServiceProvider(services, engine, options);
}

最終,它會建立並返回一個 ServiceProvider。

ServiceProviderEngine

在上面的程式碼中,ServiceProvider選擇應該使用哪個 engine, engine 是一個元件,它的功能是負責 DI容器中服務例項的建立,然後把例項注入到其他服務中。

這些是 IServiceProviderEngine 的四個實現:

  • Dynamic
  • Runtime
  • ILEmit
  • Expressions (System.Linq.Expressions)

從上面的程式碼中,我們可以看到在大多數情況下會使用 DynamicServiceProviderEngine,僅在目標框架不支援動態程式碼編譯的情況下,才使用RuntimeServiceProviderEngine,DynamicServiceProviderEngine 會使用 ILEmit 或者 Expressions 來解析服務。

我們看一下 ServiceProviderEngine 的建構函式的內容:

protected ServiceProviderEngine(IEnumerable<ServiceDescriptor> serviceDescriptors)
{
    _createServiceAccessor = CreateServiceAccessor;
    Root = new ServiceProviderEngineScope(this);
    RuntimeResolver = new CallSiteRuntimeResolver();
    CallSiteFactory = new CallSiteFactory(serviceDescriptors);
    CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
    CallSiteFactory.Add(typeof(IServiceScopeFactory), new ServiceScopeFactoryCallSite());
    RealizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>();
}

它建立一個 Root ServiceProviderEngineScope,然後傳入this, scopes限制了服務的生命週期,最常見的就是,.Net Core 收到一個介面請求時,它建立的服務就是 Scope 型別。

這種情況下,我們註冊的單例服務,它都是從 Root Scope 返回的。

然後建立一個 CallSiteRuntimeResolver,我會在接下來的文章介紹它。

最後,在上面的建構函式中,將建立一個新的ConcurrentDictionary來儲存有關服務的資訊,按需設計,只有開始使用這些服務時,它才會開始建立,如果有些服務註冊了,但是沒有使用的話,那麼它永遠不會建立。

ServiceProvider 構造方法

讓我們回到 BuildServiceProvider 方法的最後一行,它會傳入 IServiceCollection, Engine和ServiceProviderOptions:

internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, IServiceProviderEngine engine, ServiceProviderOptions options)
{
    _engine = engine;
 
    if (options.ValidateScopes)
    {
        _engine.InitializeCallback(this);
        _callSiteValidator = new CallSiteValidator();
    }
 
    if (options.ValidateOnBuild)
    {
        List<Exception> exceptions = null;
        foreach (ServiceDescriptor serviceDescriptor in serviceDescriptors)
        {
            try
            {
                _engine.ValidateService(serviceDescriptor);
            }
            catch (Exception e)
            {
                exceptions = exceptions ?? new List<Exception>();
                exceptions.Add(e);
            }
        }
 
        if (exceptions != null)
        {
            throw new AggregateException("Some services are not able to be constructed", exceptions.ToArray());
        }
    }
}

在上面的程式碼中,我們可以看到在建構函式中使用了ServiceProviderOptions, 當ValidateScopes為true時,ServiceProvider會傳入this呼叫 engine 的 InitializeCallback方法,它還建立一個新的CallSiteValidator。

如果 ValidateOnBuild 為true的話,它會檢查DI容器中已註冊的所有服務,遍歷了ServiceDescriptor 集合,然後呼叫 ValidateService, 檢查服務,並且這裡捕獲了異常,如果有錯誤,會丟擲一個聚合的異常資訊。

那麼在程式中使用 ValidateOnBuild,可以保證在程式啟動時就檢查已註冊的錯誤服務,而不是在首次解析服務時在執行時捕獲異常,這個可以很好的幫助排除問題。

ValidateService 的方法內部如下:

public void ValidateService(ServiceDescriptor descriptor)
{
    if (descriptor.ServiceType.IsGenericType && !descriptor.ServiceType.IsConstructedGenericType)
    {
        return;
    }
 
    try
    {
        ServiceCallSite callSite = CallSiteFactory.GetCallSite(descriptor, new CallSiteChain());
        if (callSite != null)
        {
            _callback?.OnCreate(callSite);
        }
    }
    catch (Exception e)
    {
        throw new InvalidOperationException($"Error while validating the service descriptor '{descriptor}': {e.Message}", e);
    }
}

總結

在本文中,我們重點介紹瞭如何從IServiceCollection來構建IServiceProvider,我們探索了一些實現細節,以瞭解如何應用ValidateScopes和ValidateOnBuild ServiceProviderOptions,我們在這篇文章中談到了很多內部程式碼,但作為庫的使用者,您不必擔心這些細節。

最重要的一點是,在IServiceCollection上呼叫BuildServiceProvider之後,將建立預設的ServiceProvider。

var serviceProvider = serviceCollection.BuildServiceProvider();

也可以傳入 ServiceProviderOptions

var serviceProviderWithOptions = serviceCollection.BuildServiceProvider(new ServiceProviderOptions
{
    ValidateOnBuild = true,
    ValidateScopes = true
});

原文連結: https://www.stevejgordon.co.uk/aspnet-core-dependency-injection-what-is-the-iserviceprovider-and-how-is-it-built

最後

歡迎掃碼關注我們的公眾號 【全球技術精選】,專注國外優秀部落格的翻譯和開源專案分享,也可以新增QQ群 897216102

探索 .NET Core 依賴注入的 IServiceProvider

相關文章