在上一篇文章中,我們學習了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
});
最後
歡迎掃碼關注我們的公眾號 【全球技術精選】,專注國外優秀部落格的翻譯和開源專案分享,也可以新增QQ群 897216102