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

短暂结语

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

posted @ 2020-04-03 14:22  YanyuWu  阅读(1078)  评论(0编辑  收藏  举报