zoukankan      html  css  js  c++  java
  • asp.netcore 深入了解配置文件加载过程

    前言

        配置文件中程序运行中,担当着不可或缺的角色;通常情况下,使用 visual studio 进行创建项目过程中,项目配置文件会自动生成在项目根目录下,如 appsettings.json,或者是被大家广泛使用的 appsettings.{env.EnvironmentName}.json;配置文件
    作为一个入口,可以让我们在不更新代码的情况,对程序进行干预和调整,那么对其加载过程的全面了解就显得非常必要。

    何时加载了默认的配置文件

    在 Program.cs 文件中,查看以下代码

     public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IWebHostBuilder <span class="hljs-title">CreateWebHostBuilder</span><span class="hljs-params">(<span class="hljs-built_in">string</span>[] args)</span> </span>=&gt;
            WebHost.CreateDefaultBuilder(args)
                .UseStartup&lt;Startup&gt;();
    }</code></pre>
    
    • WebHost.CreateDefaultBuilder 位于程序集 Microsoft.AspNetCore.dll 内,当程序执行 WebHost.CreateDefaultBuilder(args) 的时候,在 CreateDefaultBuilder 方法内部加载了默认的配置文件
      代码如下
    public static IWebHostBuilder CreateDefaultBuilder(string[] args)
            {
                var builder = new WebHostBuilder();
    
            <span class="hljs-keyword">if</span> (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
            {
                builder.UseContentRoot(Directory.GetCurrentDirectory());
            }
            <span class="hljs-keyword">if</span> (args != <span class="hljs-literal">null</span>)
            {
                builder.UseConfiguration(<span class="hljs-keyword">new</span> ConfigurationBuilder().AddCommandLine(args).Build());
            }
    
            builder.UseKestrel(<span class="hljs-function">(<span class="hljs-params">builderContext, options</span>) =&gt;</span>
                {
                    options.Configure(builderContext.Configuration.GetSection(<span class="hljs-string">"Kestrel"</span>));
                })
                .ConfigureAppConfiguration(<span class="hljs-function">(<span class="hljs-params">hostingContext, config</span>) =&gt;</span>
                {
                    <span class="hljs-keyword">var</span> env = hostingContext.HostingEnvironment;
    
                    config.AddJsonFile(<span class="hljs-string">"appsettings.json"</span>, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reloadOnChange</span>: <span class="hljs-literal">true</span>)
                          .AddJsonFile($<span class="hljs-string">"appsettings.{env.EnvironmentName}.json"</span>, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reloadOnChange</span>: <span class="hljs-literal">true</span>);
    
                    <span class="hljs-keyword">if</span> (env.IsDevelopment())
                    {
                        <span class="hljs-keyword">var</span> appAssembly = Assembly.Load(<span class="hljs-keyword">new</span> AssemblyName(env.ApplicationName));
                        <span class="hljs-keyword">if</span> (appAssembly != <span class="hljs-literal">null</span>)
                        {
                            config.AddUserSecrets(appAssembly, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>);
                        }
                    }
    
                    config.AddEnvironmentVariables();
    
                    <span class="hljs-keyword">if</span> (args != <span class="hljs-literal">null</span>)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureLogging(<span class="hljs-function">(<span class="hljs-params">hostingContext, logging</span>) =&gt;</span>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection(<span class="hljs-string">"Logging"</span>));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEventSourceLogger();
                })
                .ConfigureServices(<span class="hljs-function">(<span class="hljs-params">hostingContext, services</span>) =&gt;</span>
                {
                    <span class="hljs-comment">// Fallback</span>
                    services.PostConfigure&lt;HostFilteringOptions&gt;(<span class="hljs-function"><span class="hljs-params">options</span> =&gt;</span>
                    {
                        <span class="hljs-keyword">if</span> (options.AllowedHosts == <span class="hljs-literal">null</span> || options.AllowedHosts.Count == <span class="hljs-number">0</span>)
                        {
                            <span class="hljs-comment">// "AllowedHosts": "localhost;127.0.0.1;[::1]"</span>
                            <span class="hljs-keyword">var</span> hosts = hostingContext.Configuration[<span class="hljs-string">"AllowedHosts"</span>]?.Split(<span class="hljs-keyword">new</span>[] { <span class="hljs-string">';'</span> }, StringSplitOptions.RemoveEmptyEntries);
                            <span class="hljs-comment">// Fall back to "*" to disable.</span>
                            options.AllowedHosts = (hosts?.Length &gt; <span class="hljs-number">0</span> ? hosts : <span class="hljs-keyword">new</span>[] { <span class="hljs-string">"*"</span> });
                        }
                    });
                    <span class="hljs-comment">// Change notification</span>
                    services.AddSingleton&lt;IOptionsChangeTokenSource&lt;HostFilteringOptions&gt;&gt;(
                        <span class="hljs-keyword">new</span> ConfigurationChangeTokenSource&lt;HostFilteringOptions&gt;(hostingContext.Configuration));
    
                    services.AddTransient&lt;IStartupFilter, HostFilteringStartupFilter&gt;();
                })
                .UseIIS()
                .UseIISIntegration()
                .UseDefaultServiceProvider(<span class="hljs-function">(<span class="hljs-params">context, options</span>) =&gt;</span>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                });
    
            <span class="hljs-keyword">return</span> builder;
        }</code></pre>
    
    • 可以看到,CreateDefaultBuilder 内部还是使用了 IConfigurationBuilder 的实现,且写死了默认配置文件的名字
    public static IWebHostBuilder CreateDefaultBuilder(string[] args)
            {
                var builder = new WebHostBuilder();
    
            <span class="hljs-keyword">if</span> (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey)))
            {
                builder.UseContentRoot(Directory.GetCurrentDirectory());
            }
            <span class="hljs-keyword">if</span> (args != <span class="hljs-literal">null</span>)
            {
                builder.UseConfiguration(<span class="hljs-keyword">new</span> ConfigurationBuilder().AddCommandLine(args).Build());
            }
    
            builder.UseKestrel(<span class="hljs-function">(<span class="hljs-params">builderContext, options</span>) =&gt;</span>
                {
                    options.Configure(builderContext.Configuration.GetSection(<span class="hljs-string">"Kestrel"</span>));
                })
                .ConfigureAppConfiguration(<span class="hljs-function">(<span class="hljs-params">hostingContext, config</span>) =&gt;</span>
                {
                    <span class="hljs-keyword">var</span> env = hostingContext.HostingEnvironment;
    
                    config.AddJsonFile(<span class="hljs-string">"appsettings.json"</span>, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reloadOnChange</span>: <span class="hljs-literal">true</span>)
                          .AddJsonFile($<span class="hljs-string">"appsettings.{env.EnvironmentName}.json"</span>, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reloadOnChange</span>: <span class="hljs-literal">true</span>);
    
                    <span class="hljs-keyword">if</span> (env.IsDevelopment())
                    {
                        <span class="hljs-keyword">var</span> appAssembly = Assembly.Load(<span class="hljs-keyword">new</span> AssemblyName(env.ApplicationName));
                        <span class="hljs-keyword">if</span> (appAssembly != <span class="hljs-literal">null</span>)
                        {
                            config.AddUserSecrets(appAssembly, <span class="hljs-attr">optional</span>: <span class="hljs-literal">true</span>);
                        }
                    }
    
                    config.AddEnvironmentVariables();
    
                    <span class="hljs-keyword">if</span> (args != <span class="hljs-literal">null</span>)
                    {
                        config.AddCommandLine(args);
                    }
                })
                .ConfigureLogging(<span class="hljs-function">(<span class="hljs-params">hostingContext, logging</span>) =&gt;</span>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection(<span class="hljs-string">"Logging"</span>));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEventSourceLogger();
                })
                .ConfigureServices(<span class="hljs-function">(<span class="hljs-params">hostingContext, services</span>) =&gt;</span>
                {
                    <span class="hljs-comment">// Fallback</span>
                    services.PostConfigure&lt;HostFilteringOptions&gt;(<span class="hljs-function"><span class="hljs-params">options</span> =&gt;</span>
                    {
                        <span class="hljs-keyword">if</span> (options.AllowedHosts == <span class="hljs-literal">null</span> || options.AllowedHosts.Count == <span class="hljs-number">0</span>)
                        {
                            <span class="hljs-comment">// "AllowedHosts": "localhost;127.0.0.1;[::1]"</span>
                            <span class="hljs-keyword">var</span> hosts = hostingContext.Configuration[<span class="hljs-string">"AllowedHosts"</span>]?.Split(<span class="hljs-keyword">new</span>[] { <span class="hljs-string">';'</span> }, StringSplitOptions.RemoveEmptyEntries);
                            <span class="hljs-comment">// Fall back to "*" to disable.</span>
                            options.AllowedHosts = (hosts?.Length &gt; <span class="hljs-number">0</span> ? hosts : <span class="hljs-keyword">new</span>[] { <span class="hljs-string">"*"</span> });
                        }
                    });
                    <span class="hljs-comment">// Change notification</span>
                    services.AddSingleton&lt;IOptionsChangeTokenSource&lt;HostFilteringOptions&gt;&gt;(
                        <span class="hljs-keyword">new</span> ConfigurationChangeTokenSource&lt;HostFilteringOptions&gt;(hostingContext.Configuration));
    
                    services.AddTransient&lt;IStartupFilter, HostFilteringStartupFilter&gt;();
                })
                .UseIIS()
                .UseIISIntegration()
                .UseDefaultServiceProvider(<span class="hljs-function">(<span class="hljs-params">context, options</span>) =&gt;</span>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                });
    
            <span class="hljs-keyword">return</span> builder;
        }</code></pre>
    
    • 由于以上代码,我们可以在应用程序根目录下使用 appsettings.json 和 appsettings.{env.EnvironmentName}.json 这种形式的默认配置文件名称
      并且,由于 Main 方法默认对配置文件进行了 Build 方法的调用操作
     public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    • 我们可以在 Startup.cs 中使用注入的方式获得默认的配置文件对象 IConfigurationRoot/IConfiguration,代码片段
     public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    • 这是为什么呢,因为在 执行 Build 方法的时候,方法内部已经将默认配置文件对象加入了 ServiceCollection 中,代码片段
      var services = new ServiceCollection();
      services.AddSingleton(_options);
      services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
      services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
      services.AddSingleton(_context);
    

    var builder = new ConfigurationBuilder()
    .SetBasePath(hostingEnvironment.ContentRootPath)
    .AddConfiguration(
    config);

    configureAppConfigurationBuilder?.Invoke(context, builder);

    var configuration = builder.Build();
    services.AddSingleton<IConfiguration>(configuration);
    _context.Configuration = configuration;

    以上这段代码非常熟悉,因为在 Startup.cs 文件中,我们也许会使用过 ServiceCollection 对象将业务系统的自定义对象加入服务上下文中,以方便后续接口注入使用。

    AddJsonFile 方法的使用

        通常情况下,我们都会使用默认的配置文件进行开发,或者使用 appsettings.{env.EnvironmentName}.json 的文件名称方式来区分 开发/测试/产品 环境,根据环境变量加载不同的配置文件;可是这样一来带来了另外一个管理上的问题,产品环境的配置参数和开发环境
    是不同的,如果使用环境变量的方式控制配置文件的加载,则可能导致密码泄露等风险;诚然,可以手工在产品环境创建此文件,但是这样一来,发布流程将会变得非常繁琐,稍有错漏文件便会被覆盖。

    我们推荐使用 AddJsonFile 加载产品环境配置,代码如下

     public Startup(IConfiguration configuration, IHostingEnvironment env)
            {
                Configuration = AddCustomizedJsonFile(env).Build();
    
        }
    
        <span class="hljs-function"><span class="hljs-keyword">public</span> ConfigurationBuilder <span class="hljs-title">AddCustomizedJsonFile</span>(<span class="hljs-params">IHostingEnvironment env</span>)
        </span>{
            <span class="hljs-keyword">var</span> build = <span class="hljs-keyword">new</span> ConfigurationBuilder();
            build.SetBasePath(env.ContentRootPath).AddJsonFile(<span class="hljs-string">"appsettings.json"</span>, <span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>);
            <span class="hljs-keyword">if</span> (env.IsProduction())
            {
                build.AddJsonFile(Path.Combine(<span class="hljs-string">"/data/sites/config"</span>, <span class="hljs-string">"appsettings.json"</span>), <span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>);
            }
            <span class="hljs-keyword">return</span> build;
        }</code></pre>
    
    •     通过 AddCustomizedJsonFile 方法去创建一个 ConfigurationBuilder 对象,并覆盖系统默认的 ConfigurationBuilder 对象,在方法内部,默认加载开发环境的配置文件,在产品模式下,额外加载目录 /data/sites/config/appsettings.json 文件,
      不同担心配置文件冲突问题,相同键值的内容将由后加入的配置文件所覆盖。

    配置文件的变动

    • 在调用 AddJsonFile 时,我们看到该方法共有 5 个重载的方法
      其中一个方法包含了 4 个参数,代码如下
     public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, IFileProvider provider, string path, bool optional, bool reloadOnChange)
            {
                if (builder == null)
                {
                    throw new ArgumentNullException(nameof(builder));
                }
                if (string.IsNullOrEmpty(path))
                {
                    throw new ArgumentException(Resources.Error_InvalidFilePath, nameof(path));
                }
    
            <span class="hljs-keyword">return</span> builder.AddJsonFile(s =&gt;
            {
                s.FileProvider = provider;
                s.Path = path;
                s.Optional = optional;
                s.ReloadOnChange = reloadOnChange;
                s.ResolveFileProvider();
            });
        }</code></pre>
    
    •     在此方法中,有一个参数 bool reloadOnChange,从参数描述可知,该值指示在文件变动的时候是否重新加载,默认值为:false;一般在手动加载配置文件,即调用 AddJsonFile 方法时,建议将该参数值设置为 true。
      那么 .netcore 是如果通过该参数 reloadOnChange 是来监控文件变动,以及何时进行重新加载的操作呢,看下面代码
            public IConfigurationRoot Build()
            {
                var providers = new List<IConfigurationProvider>();
                foreach (var source in Sources)
                {
                    var provider = source.Build(this);
                    providers.Add(provider);
                }
                return new ConfigurationRoot(providers);
            }
    • 在我们执行 .Build 方法的时候,方法内部最后一行代码给我们利用 AddJsonFile 方法的参数创建并返回了一个 ConfigurationRoot 对象
      在 ConfigurationRoot 的构造方法中
            public ConfigurationRoot(IList<IConfigurationProvider> providers)
            {
                if (providers == null)
                {
                    throw new ArgumentNullException(nameof(providers));
                }
    
            _providers = providers;
            foreach (var p <span class="hljs-keyword">in</span> providers)
            {
                p.Load();
                ChangeToken.OnChange(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> p.GetReloadToken(), <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> RaiseChanged());
            }
        }</code></pre>
    
    • 我们看到,方法内部一次读取了通过 AddJsonFile 方法加入的配置文件,并为每个配置文件单独分配了一个监听器 ChangeToken,并绑定当前文件读取对象 IConfigurationProvider.GetReloadToken 方法到监听器中
      当文件产生变动的时候,监听器会收到一个通知,同时,对该文件执行原子操作
     private void RaiseChanged()
            {
                var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
                previousToken.OnReload();
            }
    • 由于 AddJsonFile 方法内部使用了 JsonConfigurationSource ,而 Build 的重载方法构造了一个 JsonConfigurationProvider 读取对象,查看代码
            public override IConfigurationProvider Build(IConfigurationBuilder builder)
            {
                EnsureDefaults(builder);
                return new JsonConfigurationProvider(this);
            }
    • 在 JsonConfigurationProvider 继承自 FileConfigurationProvider 类,该类位于程序集 Microsoft.Extensions.Configuration.Json.dll 内
      在 FileConfigurationProvider 的构造方法中实现了监听器重新加载配置文件的过程
            public FileConfigurationProvider(FileConfigurationSource source)
            {
                if (source == null)
                {
                    throw new ArgumentNullException(nameof(source));
                }
                Source = source;
    
            <span class="hljs-keyword">if</span> (Source.ReloadOnChange &amp;&amp; Source.FileProvider != <span class="hljs-literal">null</span>)
            {
                ChangeToken.OnChange(
                    <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> Source.FileProvider.Watch(Source.Path),
                    <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
                        Thread.Sleep(Source.ReloadDelay);
                        Load(reload: <span class="hljs-literal">true</span>);
                    });
            }
        }</code></pre>
    

    值得注意的是,该监听器不是在得到文件变动通知后第一时间去重新加载配置文件,方法内部可以看到,这里有一个 Thread.Sleep(Source.ReloadDelay),而 ReloadDelay 的默认值为:250ms,该属性的描述为

    • 获取或者设置重新加载将等待的毫秒数, 然后调用 "Load" 方法。 这有助于避免在完全写入文件之前触发重新加载。默认值为250
    • 让人欣慰的是,我们可以自定义该值,如果业务对文件变动需求不是特别迫切,您可以将该值设置为一个很大的时间,通常情况下,我们不建议那么做

    结语

        以上就是 asp.netcore 中配置文件加载的内部执行过程,从中我们认识到,默认配置文件是如何加载,并将默认配置文件如何注入到系统中的,还学习到了如果在不同的环境下,选择加载自定义配置文件的过程;但配置文件变动的时候,系统内部又是如何去把配置文件重新加载到内存中去的。

  • 相关阅读:
    高斯过程(GP)
    隐马尔可夫模型(hidden Markov model,HMM)
    php常用配置(php.ini)
    MySQL常用配置
    Discuz论坛搭建过程
    MySQL安装及主从配置
    rsync+inotify实现服务器数据同步
    文件解压缩及打包工具
    vim编辑器的基本操作
    linux笔记_磁盘分区
  • 原文地址:https://www.cnblogs.com/owenzh/p/11213996.html
Copyright © 2011-2022 走看看