• 浅谈 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的实例. 那么事实上也确实如此:

    短暂结语

    容器能够做到解耦, 解耦就是类与类之间如果有依赖, 就通过容器创建类的依赖.

  • 相关阅读:
    django 定时任务 django-crontab 的使用
    Django中更新多个对象数据与删除对象的方法
    Django复制记录的方法
    Python中关于日期的计算总结
    django中添加日志功能
    Python 日期时间datetime 加一天,减一天,加减一小时一分钟,加减一年
    根据后端传的时间前端js进行倒计时
    输入pip命令报错:from pip import main ImportError: cannot import name 'main'
    操作uwsgi命令
    关于linux下安装mysqlclient报 Failed building wheel for mysqlclient问题
  • 原文地址:https://www.cnblogs.com/it-dennis/p/12626272.html
Copyright © 2020-2023  润新知