前言
筆者的這篇文章和上篇文章思路一樣,不注重依賴注入的使用方法,更加註重原始碼的實現,我儘量的表達清楚內容,讓讀者能夠真正的學到東西。如果有不太清楚依賴注入是什麼或怎麼在.Net專案中使用的話,請點選這裡,這是微軟的官方文件,把用法介紹的很清晰了,相信你會有很大收穫。那麼廢話不多說,我們們進入正題(可能篇幅有點長,耐心讀完你會有收穫的?)。
DependencyInjection類之間的關係
下圖中只列舉重要的類和介面(實際的類和介面有很多),裡面的方法和屬性也只列出重要的,這裡只是讓你有個大概的印象,看不懂沒關係,繼續往下讀。
原始碼解析
- 首先我們要知道,.Net程式在啟動的時候會構建一個Host(Host.Build().Run()),在其Build方法中來構建容器(只構建根容器,下面會解釋),具體在哪構建容器,原始碼如下圖:
public class HostBuilder : IHostBuilder
{
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
//即我們在main函式中看到的Build方法
public IHost Build()
{
//只能執行一次這個方法
if (_hostBuilt)
{
throw new InvalidOperationException(SR.BuildCalled);
}
_hostBuilt = true;
using var diagnosticListener = new DiagnosticListener("Microsoft.Extensions.Hosting");
const string hostBuildingEventName = "HostBuilding";
const string hostBuiltEventName = "HostBuilt";
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName))
{
Write(diagnosticListener, hostBuildingEventName, this);
}
//執行Host配置(應用程式執行路徑,增加_dotnet環境變數,獲取命令列引數,載入預配置)
BuildHostConfiguration();
//設定主機環境變數
CreateHostingEnvironment();
//設定上下文
CreateHostBuilderContext();
//構建程式配置(載入appsetting.json)
BuildAppConfiguration();
//構造容器,載入服務
CreateServiceProvider();
var host = _appServices.GetRequiredService<IHost>();
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName))
{
Write(diagnosticListener, hostBuiltEventName, host);
}
return host;
}
private void CreateServiceProvider()
{
//構建服務集合
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_ => _appConfiguration);
services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
AddLifetime(services);
services.AddSingleton<IHost>(_ =>
{
return new Internal.Host(_appServices,
_hostingEnvironment,
_defaultProvider,
_appServices.GetRequiredService<IHostApplicationLifetime>(),
_appServices.GetRequiredService<ILogger<Internal.Host>>(),
_appServices.GetRequiredService<IHostLifetime>(),
_appServices.GetRequiredService<IOptions<HostOptions>>());
});
services.AddOptions().Configure<HostOptions>(options => { options.Initialize(_hostConfiguration); });
services.AddLogging();
//載入StartUp裡面的ConfigService方法
foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
//構建容器
object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
//配置容器介面卡,可以構建自己的容器
foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
{
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
//生成容器服務
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
if (_appServices == null)
{
throw new InvalidOperationException(SR.NullIServiceProvider);
}
//在根容器中先生成IConfiguration
_ = _appServices.GetService<IConfiguration>();
}
}
從上面可以看出,_serviceProviderFactory實際上就是DefaultServiceProviderFactory物件例項,它通過呼叫自己的CreateServiceProvider方法,來構建預設容器,而預設容器是作為根容器存在,全域性只有一個。
- _serviceProviderFactory.CreateBuilder(services)方法只是簡單的轉換(把IServiceCollection轉化成Object),為什麼他要返回object呢?因為在.net中預設實現是個IServiceCollection,那麼如果要是需要自定義容器的話,則需要通過自己的ProviderFactory返回自己的Builder,來生成容器,在這裡就不細講了,以後其他文章會詳細說下怎麼自定義一個IOC,這裡只講預設實現,接下來我們具體講它是怎麼生成容器服務的,請看CreateServiceProvider方法實現:
//.net提供的預設的ProviderFactory
public class DefaultServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
private readonly ServiceProviderOptions _options;
public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default)
{
}
public DefaultServiceProviderFactory(ServiceProviderOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options;
}
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return services;
}
//通過呼叫擴充套件方法,來實現ServiceProvider
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
{
return containerBuilder.BuildServiceProvider(_options);
}
}
public static class ServiceCollectionContainerBuilderExtensions
{
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));
}
return new ServiceProvider(services, options);
}
}
public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable
{
//驗證Scoped服務是否有在Singleton服務中存在,預設是關閉的,因為可能會把Scoped服務提升為Singleton服務,那麼這個本身就是不正常的呼叫,建議修改呼叫方法。
private readonly CallSiteValidator _callSiteValidator;
//用來獲取服務
private readonly Func<Type, Func<ServiceProviderEngineScope, object>> _createServiceAccessor;
//服務提供的引擎
internal ServiceProviderEngine _engine;
//是否已被釋放
private bool _disposed;
//對每種type做了一個快取,來提高效率
private ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> _realizedServices;
//服務描述工廠,儲存著服務描述的集合,描述服務的型別
internal CallSiteFactory CallSiteFactory { get; }
//根容器
internal ServiceProviderEngineScope Root { get; }
internal static bool VerifyOpenGenericServiceTrimmability { get; } =
AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false;
internal ServiceProvider(ICollection<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
{
//根容器就是本身,並且IsRootScope是true標識
Root = new ServiceProviderEngineScope(this, isRootScope: true);
//獲取用那種引擎來獲取服務
_engine = GetEngine();
//獲取服務時呼叫的函式
_createServiceAccessor = CreateServiceAccessor;
_realizedServices = new ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>>();
//主要是生成服務的ServiceCallSite這個很重要,用來描述服務型別
CallSiteFactory = new CallSiteFactory(serviceDescriptors);
//額外的一些內部服務描述
CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite());
CallSiteFactory.Add(typeof(IServiceScopeFactory), new ConstantCallSite(typeof(IServiceScopeFactory), Root));
CallSiteFactory.Add(typeof(IServiceProviderIsService), new ConstantCallSite(typeof(IServiceProviderIsService), CallSiteFactory));
if (options.ValidateScopes)
{
_callSiteValidator = new CallSiteValidator();
}
//在build階段就驗證服務注入是否正確
if (options.ValidateOnBuild)
{
List<Exception> exceptions = null;
foreach (ServiceDescriptor serviceDescriptor in serviceDescriptors)
{
try
{
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());
}
}
}
public object GetService(Type serviceType) => GetService(serviceType, Root);
private void OnCreate(ServiceCallSite callSite)
{
_callSiteValidator?.ValidateCallSite(callSite);
}
private void OnResolve(Type serviceType, IServiceScope scope)
{
_callSiteValidator?.ValidateResolution(serviceType, scope, Root);
}
internal object GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
Func<ServiceProviderEngineScope, object> realizedService = _realizedServices.GetOrAdd(serviceType, _createServiceAccessor);
OnResolve(serviceType, serviceProviderEngineScope);
DependencyInjectionEventSource.Log.ServiceResolved(this, serviceType);
var result = realizedService.Invoke(serviceProviderEngineScope);
System.Diagnostics.Debug.Assert(result is null || CallSiteFactory.IsService(serviceType));
return result;
}
private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
{
ServiceCallSite callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
if (callSite != null)
{
DependencyInjectionEventSource.Log.CallSiteBuilt(this, serviceType, callSite);
OnCreate(callSite);
// Optimize singleton case
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
{
object value = CallSiteRuntimeResolver.Instance.Resolve(callSite, Root);
return scope => value;
}
return _engine.RealizeService(callSite);
}
return _ => null;
}
internal IServiceScope CreateScope()
{
if (_disposed)
{
ThrowHelper.ThrowObjectDisposedException();
}
return new ServiceProviderEngineScope(this, isRootScope: false);
}
private ServiceProviderEngine GetEngine()
{
ServiceProviderEngine engine;
#if NETFRAMEWORK || NETSTANDARD2_0
engine = new DynamicServiceProviderEngine(this);
#else
if (RuntimeFeature.IsDynamicCodeCompiled)
{
engine = new DynamicServiceProviderEngine(this);
}
else
{
engine = RuntimeServiceProviderEngine.Instance;
}
#endif
return engine;
}
}
從上面的程式碼中可以看出,在構建ServiceProvider時,主要是構造預設的根容器和採用哪種引擎來獲取服務,並且把服務的型別描述給構建好並快取起來。我們再來看看GetService方法,裡面的呼叫服務分為Root和非Root,也就是說,需要區分哪些是屬於根容器的,哪些不是屬於根容器的。在.Net中,預設情況下新增的新增的Singleton型別屬於Root,Scoped型別屬於Scope,Transient型別屬於Dispose,具體請看下面程式碼:
internal struct ResultCache
{
public ResultCache(ServiceLifetime lifetime, Type type, int slot)
{
switch (lifetime)
{
case ServiceLifetime.Singleton:
Location = CallSiteResultCacheLocation.Root;
break;
case ServiceLifetime.Scoped:
Location = CallSiteResultCacheLocation.Scope;
break;
case ServiceLifetime.Transient:
Location = CallSiteResultCacheLocation.Dispose;
break;
default:
Location = CallSiteResultCacheLocation.None;
break;
}
Key = new ServiceCacheKey(type, slot);
}
public CallSiteResultCacheLocation Location { get; set; }
public ServiceCacheKey Key { get; set; }
}
- 有了基本的瞭解之後,我們再來看通過GetService方法獲取物件例項,當型別是位於根容器時,會將根容器的例項做在快取裡面,而型別如果是Transient,那麼則每次獲取時,都重新建立,不做快取處理,至於Scoped方式的型別,稍後再說,我們先看下面程式碼
internal sealed class CallSiteRuntimeResolver : CallSiteVisitor<RuntimeResolverContext, object>
{
//對於Transient的型別例項
protected override object VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
{
//直接構造型別例項,並記錄在dispose的集合中,等待容器被Dispose時,同時dispose掉
return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context));
}
//獲取在根容器的物件例項
protected override object VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
//對於已經做過快取的服務,直接返回
if (callSite.Value is object value)
{
return value;
}
var lockType = RuntimeResolverLock.Root;
ServiceProviderEngineScope serviceProviderEngine = context.Scope.RootProvider.Root;
//對當前服務型別上鎖
lock (callSite)
{
//相當於一個雙檢鎖,再查一遍
if (callSite.Value is object resolved)
{
return resolved;
}
resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
serviceProviderEngine.CaptureDisposable(resolved);
//將獲取到的例項做快取,返回拿到的例項
callSite.Value = resolved;
return resolved;
}
}
}
- 而對於注入的Scope型別,我們知道針對每次請求下,針對每個型別的例項是一個,而在ServiceProviderEngineScope類中有一個CreateScope方法,而每次接收到請求時都會構建HttpContext例項,在構建的同時呼叫CreateScope方法,來構建新的容器作為本次請求的Scope容器,期間獲取的Scope型別的例項,都是在裡面獲取,先做快取再返回,保證這個作用域內的例項是一個,請看下面程式碼:
internal sealed class CallSiteRuntimeResolver : CallSiteVisitor<RuntimeResolverContext, object>
{
protected override object VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
{
//如果是根容器作用域,就在根容器中找,否則就在當前作用域容器中找
return context.Scope.IsRootScope ?
VisitRootCache(callSite, context) :
VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope);
}
private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
{
bool lockTaken = false;
object sync = serviceProviderEngine.Sync;
Dictionary<ServiceCacheKey, object> resolvedServices = serviceProviderEngine.ResolvedServices;
if ((context.AcquiredLocks & lockType) == 0)
{
Monitor.Enter(sync, ref lockTaken);
}
try
{
//每次查詢前,先判斷這個例項是否已經建立過,如果在本容器的快取集合中存在就直接返回
if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved))
{
return resolved;
}
resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext
{
Scope = serviceProviderEngine,
AcquiredLocks = context.AcquiredLocks | lockType
});
serviceProviderEngine.CaptureDisposable(resolved);
//將型別例項新增到快取中
resolvedServices.Add(callSite.Cache.Key, resolved);
return resolved;
}
finally
{
if (lockTaken)
{
Monitor.Exit(sync);
}
}
}
}
總結
通過原始碼可以看出預設的依賴注入有以下特點:
- 在一個Host中只能存在一個根容器,而其他容器(每次請求建立)都是從根容器中衍生出來的。
- Scope的型別可能被提升到Singleton。
- 對於Singleton的注入型別,都是存放在根容器中,並作快取。
- 對於Scoped的注入型別,大部分是存放在每次請求構建的容器中,並作快取。
- 對於Transient的注入型別,則不做快取,每次訪問都構建出一個新的物件例項。
關於.Net中預設的依賴注入,上面的程式碼也只是挑出重點的部分分享給大家,具體想看更多細節,讀者可以根據本篇部落格直接看原始碼,因為篇幅問題,實在不能貼太多的程式碼,主要是把思路給大家說一下。