.net core 源码分析(3) 启动过程-InitializeHostConfiguration
关于Hosting展示代码来源 .net core的runtime-8.0.7源码:https://github.com/dotnet/runtime
/// <summary> /// A program initialization utility. /// </summary> public partial class HostBuilder : IHostBuilder { private const string HostBuildingDiagnosticListenerName = "Microsoft.Extensions.Hosting"; private const string HostBuildingEventName = "HostBuilding"; private const string HostBuiltEventName = "HostBuilt"; //这部分代码在本系列文章第一章已经描述,此处不过多介绍 private readonly List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>(); private readonly List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>(); private readonly List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>(); private readonly List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>(); private IServiceFactoryAdapter _serviceProviderFactory; //这些在属性会在Build()方法中不同阶段中被创建 private bool _hostBuilt; private IConfiguration? _hostConfiguration; private IConfiguration? _appConfiguration; private HostBuilderContext? _hostBuilderContext; private HostingEnvironment? _hostingEnvironment; private IServiceProvider? _appServices; private PhysicalFileProvider? _defaultProvider; /// <summary> /// Initializes a new instance of <see cref="HostBuilder"/>. /// </summary> public HostBuilder() { //构造函数中默认 初始化 ServiceProviderFactory,不过可以通过HostBuilder.UseServiceProviderFactory方法覆盖掉 _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory()); } }
再来回顾一下HostBuilder的Build过程中都经历了哪些阶段。
public partial class HostBuilder : IHostBuilder { /// <summary> /// Run the given actions to initialize the host. This can only be called once. /// </summary> /// <returns>An initialized <see cref="IHost"/></returns> /// <remarks>Adds basic services to the host such as application lifetime, host environment, and logging.</remarks> public IHost Build() { if (_hostBuilt) { throw new InvalidOperationException(SR.BuildCalled); } _hostBuilt = true; // REVIEW: If we want to raise more events outside of these calls then we will need to // stash this in a field. using DiagnosticListener diagnosticListener = LogHostBuilding(this); // 1.初始化主机(Host)配置 InitializeHostConfiguration(); // 2.初始化 HostingEnvironment InitializeHostingEnvironment(); // 3.初始化 HostBuilderContext InitializeHostBuilderContext(); // 4.初始化应用(App)配置 InitializeAppConfiguration(); // 5.初始化服务并创建 Service Provider InitializeServiceProvider(); return ResolveHost(_appServices, diagnosticListener); } }
首先看一下第一阶段初始化HostConfiguration
[MemberNotNull(nameof(_hostConfiguration))] private void InitializeHostConfiguration() { IConfigurationBuilder configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions) { buildAction(configBuilder); }
//此阶段结束,私有属性 _hostConfiguration已经被创建,在第二个阶段中就可以使用了。 _hostConfiguration = configBuilder.Build(); }
InitializeHostConfiguration()主要用以初始化系统的配置对象,通过 ConfigurationBuilder 来构建 ICongfigurationRoot, 下面展示一下 Configuration模块在源码中的项目结构。
ConfigurationBuilder
类的主要职责
ConfigurationBuilder
类用于构建和初始化 IConfigurationRoot
实例,该实例随后用于应用程序中以访问配置数据。ConfigurationBuilder
通过链式方法允许你添加配置源(如appsettings.json、环境变量、命令行参数等),然后你可以通过 Build()
方法来生成最终的配置对象。配置源:CommandLine、EnvironmentVariables、Ini、JSon、Xml、UserSecrets等。
如果我们自己设计配置系统
试想一下,如果让我们来设计配置信息系统, 用户可以多种方式配置信息,可以通过xml文件,json文件,也可以把数据库中的数据读取出来放入配置信息中,读取电脑本地环境变量存在到配置信息中。既然多种类型的配置信息都可能存在,那么我们首先就会想到Provider,每种类型一个Provider .
暂且我们就设计JsonProvider ,XmlProvider,EnvirmentVarianlesProvider , 我们还需要一个管理类来存放用户注册的Provider , 我们把他叫做 ProviderManager,ProviderManager提供一个AddProvider(IProvider)。
ProviderManager.AddProvider(new JsonProvider())
但是这时候我们才发现,在new JsonProvider(),我们需要在构造函数中添加额外的信息,比如文件地址或者其他配置,每个Provider都有可能需要自己相关的初始化信息。
ProviderManager.AddProvider(new JsonProvider( string fileName, xxxx)) ProviderManager.AddProvider(new MemoryProvider(string xxx))
来看一下JsonProvider真实的添加情况
/// <summary> /// Represents a JSON file as an <see cref="IConfigurationSource"/>. /// </summary> public class JsonConfigurationSource : FileConfigurationSource { /// <summary> /// Builds the <see cref="JsonConfigurationProvider"/> for this source. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param> /// <returns>A <see cref="JsonConfigurationProvider"/></returns> public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider(this); } }
这里在new JsonConfigurationProvider运行了部分逻辑EnsureDefaults,JsonConfigurationProvider 构造函数参数也不仅仅需要一个字符串这种简单的参数,显然这种情况如果每次用户添加Provider 的时候需要显示的执行特定逻辑不是那么适合,所以我们要升级我们的设计, 我们不直接添加Provider ,我们添加ProviderBuilder 来隐藏new provider 的过程。ProviderBuilder也可以称为ProviderFactory.总之该类的工作就是创建对应的Provider。所以我们添加配置的设计改为
//升级前 ProviderManager.AddProvider(new JsonProvider( string fileName, xxxx)) ProviderManager.AddProvider(new MemoryProvider(string xxx)) //升级后 ProviderManager.Add(new JsonProviderBuilder( string fileName, xxxx))
.net core 中把ProviderBuilder 抽象为IConfigurationSource
/// <summary> /// Represents a source of configuration key/values for an application. /// </summary> public interface IConfigurationSource { /// <summary> /// Builds the <see cref="IConfigurationProvider"/> for this source. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param> /// <returns>An <see cref="IConfigurationProvider"/></returns> IConfigurationProvider Build(IConfigurationBuilder builder); }
/// <summary> /// Represents environment variables as an <see cref="IConfigurationSource"/>. /// </summary> public class EnvironmentVariablesConfigurationSource : IConfigurationSource { /// <summary> /// A prefix used to filter environment variables. /// </summary> public string? Prefix { get; set; } /// <summary> /// Builds the <see cref="EnvironmentVariablesConfigurationProvider"/> for this source. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param> /// <returns>A <see cref="EnvironmentVariablesConfigurationProvider"/></returns> public IConfigurationProvider Build(IConfigurationBuilder builder) { return new EnvironmentVariablesConfigurationProvider(Prefix); } }
下面来看一下ConfigurationBuilder的Build方法,
public class ConfigurationBuilder : IConfigurationBuilder { private readonly List<IConfigurationSource> _sources = new(); //前面介绍的AddProvider的地方换成AddProviderBuilder /// <summary> /// Returns the sources used to obtain configuration values. /// </summary> public IList<IConfigurationSource> Sources => _sources; /// <summary> /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/> /// and the registered <see cref="IConfigurationProvider"/>s. /// </summary> public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>(); /// <summary> /// Adds a new configuration source. /// 用户注册各种不同的配置信息,json文件的,xml的,环境变量的 /// </summary> /// <param name="source">The configuration source to add.</param> /// <returns>The same <see cref="IConfigurationBuilder"/>.</returns> public IConfigurationBuilder Add(IConfigurationSource source) { ThrowHelper.ThrowIfNull(source); _sources.Add(source); return this; } /// <summary> /// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in /// <see cref="Sources"/>. /// </summary> ///Build过程中,每个Provider就会进行初始化配置信息的逻辑,比如Json文件的Provider就读取josn文件内容到内存中,环境变量的Provider就读取本地环境变量到内存中 /// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns> public IConfigurationRoot Build() { var providers = new List<IConfigurationProvider>(); foreach (IConfigurationSource source in _sources) { IConfigurationProvider provider = source.Build(this); providers.Add(provider); } //所有的Provider都被放在ConfigurationRoot中, //每个Provider中持有一个私有字典 用于存放 配置信息,key-value return new ConfigurationRoot(providers); } }
到了这里相信大家已经弄明白.net core 配置文件是怎么回事了。
书接上回
所有说在HostBuilder.Build的第一阶段 InitializeHostConfiguration(); 核心主要是进行了配置文件的初始化,构造了 _hostConfiguration = new ConfigurationRoot(providers);
回顾一下HostBuild暂存的 List<Action<IConfigurationBuilder>> _configureHostConfigActions ,可以通过执行 Action 实现添加ConfigurationSource到ConfigurationBuilder中
public class HostBuilder : IHostBuilder { private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>(); private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>(); private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>(); public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate) { _configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate) { _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate) { _configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } }