浅谈 asp.net core 内置容器
本篇已收录至 asp.net core 随笔系列
通过阅读本文, 希望你能够了解以下内容:
- build-in的容器是何时, 如何创建出来的?
- build-in容器提供注册服务的方法都有哪些?
- build-in容器内Service的生命周期都有哪些?
- service怎么添加进容器里面的?
- startup.cs中ConfigureService()是什么时候调用的?
- 如何从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的实例. 那么事实上也确实如此:
短暂结语
容器能够做到解耦, 解耦就是类与类之间如果有依赖, 就通过容器创建类的依赖.