淺談 asp.net core 內置容器


本篇已收錄至 asp.net core 隨筆系列

通過閱讀本文, 希望你能夠了解以下內容:

  1. build-in的容器是何時, 如何創建出來的?
  2. build-in容器提供注冊服務的方法都有哪些?
  3. build-in容器內Service的生命周期都有哪些?
  4. service怎么添加進容器里面的?
  5. startup.cs中ConfigureService()是什么時候調用的?
  6. 如何從ServiceProvider中獲取service?

build-in的容器是何時如何創建出來的?

  • CreateDefaultBuilder 創建 HostBuilder 時調用了 UseDefaultServiceProvider

  • 然后UseDefaultServiceProvider調用DefaultServiceProviderFactory

  • 最后初始化 _serviceProviderFactory

  • HostBuilder 在執行 Build 方法時, 首先使用 _serviceProviderFactory 以及 service Collection 創建出 containerBuilder, 然后繼續使用 _serviceProviderFactory 根據 containerBuilder 創建出 container

  • containerBuilder 以及 container (service provider) 的創建如圖:

  • 最后創建 container (service provider) 的方法如下, 這里是容器IOC最后初始化的方法, 如果深入研究的話, 可以了解更深層次的容器理念:

        internal ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors, ServiceProviderOptions options)
        {
            IServiceProviderEngineCallback callback = null;
            if (options.ValidateScopes)
            {
                callback = this;
                _callSiteValidator = new CallSiteValidator();
            }

            switch (options.Mode)
            {
                case ServiceProviderMode.Default:
#if !NETCOREAPP
                    _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
#else
                    if (RuntimeFeature.IsSupported("IsDynamicCodeCompiled"))
                    {
                        _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
                    }
                    else
                    {
                        // Don't try to compile Expressions/IL if they are going to get interpreted
                        _engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
                    }
#endif
                    break;
                case ServiceProviderMode.Dynamic:
                    _engine = new DynamicServiceProviderEngine(serviceDescriptors, callback);
                    break;
                case ServiceProviderMode.Runtime:
                    _engine = new RuntimeServiceProviderEngine(serviceDescriptors, callback);
                    break;
#if IL_EMIT
                case ServiceProviderMode.ILEmit:
                    _engine = new ILEmitServiceProviderEngine(serviceDescriptors, callback);
                    break;
#endif
                case ServiceProviderMode.Expressions:
                    _engine = new ExpressionsServiceProviderEngine(serviceDescriptors, callback);
                    break;
                default:
                    throw new NotSupportedException(nameof(options.Mode));
            }

            if (options.ValidateOnBuild)
            {
                List<Exception> exceptions = null;
                foreach (var 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());
                }
            }
        }
  • container (service provider) 創建完畢.

build-in容器提供注冊服務的方法都有哪些?

ServiceCollection 是承載 service 的集合, 上面提到的 service Provider 也是由它創建的, 看一下類的結構和它的擴展方法:

build-in容器內Service的生命周期都有哪些?

build-in的容器提供三個生命周期:

生命周期 des
Singleton 整個 web app 啟動后, 這個 service 只會被創建一次, 所以只有一個實例, web app 停止時, service的實例才被釋放.
Scope scope 是指 client 發送 request 到 server, 以及 server 做出 response 響應的這一過程; 每次 scope 創建后都會創建一個新的實例, scope結束后, service的實例被釋放.
Transient 每次需要這個 service時, 就會創建出一個新的實例, 用完后就會被釋放. 但是注意, 不要將實現了IDispose接口的服務注冊為瞬時的生命周期, 因為如果一旦這個服務被在根服務中使用, 每次調用都會創建出新的實例, 但是不會被釋放直到應用程序關閉. 這種疏忽很容易造成內存溢出.(從極客中學的)

service怎么添加進容器里面的?

  • 首先將一些內置的service添加到ServiceCollection
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>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost, Internal.Host>();
services.AddOptions();
services.AddLogging();
  • 然后將_configureServicesActions集合中的services添加到ServiceCollection
foreach (var configureServicesAction in _configureServicesActions)
{
    configureServicesAction(_hostBuilderContext, services);
}
  • _configureServicesActions 是 HostBuilder 在構建的時候, 調用 ConfigureServices 配置的
/// Adds services to the container. This can be called multiple times and the results will be additive.
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
    _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
    return this;
}
  • 外部開發者配置service一般是在startup.cs文件中, 程序啟動時, program.cs 中 ConfigureWebHostDefaults 里面的 configure 的委托傳入的是 WebServer.useStartUp<StartUp>(), 看一下它的源碼:

  • 源碼貼出來了, 這不是 webHostBuilder 調用的 UseStartUp 嗎. 怎么將 service 放到 HostBuilder 里面的 _configureServicesActions 里面的呢? 其實很簡單:

startup.cs中ConfigureService()是什么時候調用的?

這個內容有點超綱了, 能看懂多少算多少吧.

在 webHostBuilder調用useStartup里, 有這樣一段代碼看起來像是將我們的startup類, 根據協定, 獲取到 ConfigureServices, ConfigureContainer, Configure 這三個方法:

services.AddSingleton(typeof(IStartup), sp =>
{
    var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
    return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
});

上面的內容總結如下:

services.AddSingleton(typeof(IStartup), ConventionBasedStartup)

那么具體怎么將調用的startup里面的ConfigureService呢? 感興趣的同學去看第一節里面的這段代碼, 有驚喜:

_engine.ValidateService(serviceDescriptor);

如何從ServiceProvider中獲取service?

HostBuilder 執行 Build 方法的返回值為 IHost 的實例, 即從容器中獲取 IHost 的實現類的實例:

return _appServices.GetRequiredService<IHost>();

PS: 有一些是通過 services.GetService(), 實際上也是在最后調的 GetRequiredService()

public static object GetRequiredService(this IServiceProvider provider, Type serviceType)
{
    if (provider == null)
            throw new ArgumentNullException(nameof(provider));
    if (serviceType == null)
            throw new ArgumentNullException(nameof(serviceType));

    ///如果能夠從 supported required service中獲取到service, 則說明是從第三方容器內獲取的 service
    var requiredServiceSupportingProvider = provider as ISupportRequiredService;
    if (requiredServiceSupportingProvider != null)
        return requiredServiceSupportingProvider.GetRequiredService(serviceType);
    /// 如果獲取不到, 就得從build-in的容器中獲取
    var service = provider.GetService(serviceType);
    if (service == null)
        throw new InvalidOperationException(Resources.FormatNoServiceRegistered(serviceType));

    return service;
}

這個 Host 不是上一篇 program.cs文件中提到的靜態類 Host, 而是一個 internal 類, 繼承 IHost 接口. 這個Host的代碼貼到下面:

也就是說, 當容器注冊進一個IHost的實現類的實例時, 必然要通過實現類的構造函數創建實例, 那么構造函數中需要的service, 也必須注冊進入容器當中, 否則就無法構造出IHost的實例. 那么事實上也確實如此:

短暫結語

容器能夠做到解耦, 解耦就是類與類之間如果有依賴, 就通過容器創建類的依賴.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM