原始碼解析.Net中DependencyInjection的實現

SnailZz發表於2021-08-31

前言

筆者的這篇文章和上篇文章思路一樣,不注重依賴注入的使用方法,更加註重原始碼的實現,我儘量的表達清楚內容,讓讀者能夠真正的學到東西。如果有不太清楚依賴注入是什麼或怎麼在.Net專案中使用的話,請點選這裡,這是微軟的官方文件,把用法介紹的很清晰了,相信你會有很大收穫。那麼廢話不多說,我們們進入正題(可能篇幅有點長,耐心讀完你會有收穫的?)。

DependencyInjection類之間的關係

下圖中只列舉重要的類和介面(實際的類和介面有很多),裡面的方法和屬性也只列出重要的,這裡只是讓你有個大概的印象,看不懂沒關係,繼續往下讀。

原始碼解析

  1. 首先我們要知道,.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方法,來構建預設容器,而預設容器是作為根容器存在,全域性只有一個。

  1. _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; }
}
  1. 有了基本的瞭解之後,我們再來看通過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;
      }
  }
}
  1. 而對於注入的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中預設的依賴注入,上面的程式碼也只是挑出重點的部分分享給大家,具體想看更多細節,讀者可以根據本篇部落格直接看原始碼,因為篇幅問題,實在不能貼太多的程式碼,主要是把思路給大家說一下。

相關文章