本篇已收錄至 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的實例. 那么事實上也確實如此:
短暫結語
容器能夠做到解耦, 解耦就是類與類之間如果有依賴, 就通過容器創建類的依賴.