一 背景

相比.Net Framework , .NET Core的配置系统 ,有一些明显的优点 ,如:

1 支持更丰富的配置源

2 读取配置时, 可以用相同的方式读取, 并且非常方便

3 修改配置后,不用重启应用

本期对配置相关的源码简单梳理一下。 只说主逻辑 ,并且从用的角度说起 ,逐步深入。

二 配置读取

2.1 举个栗子

进入上代码环节 ,新建控制台应用 , 只需要引入 "Microsoft.Extensions.Configuration" 1个nuget包 ,就可以使用基本的配置了 。

(不得不说,相较于.NET Framework,.Net Core 的组件化设计实现的相当彻底 )

github地址

//数据源
var dicData = new Dictionary<string, string>() { { "Key1", "Value1" }, { "Key2", "Value2" } }; IConfigurationRoot configurationRoot = new ConfigurationBuilder()
  .Add(new MemoryConfigurationSource() { InitialData = dicData }) // 添加配置源
  .Build(); // 构建配置
Console.WriteLine("Key1=" + configurationRoot["Key1"]);//输出内容 Key1=Value1

我们好奇 configurationRoot["Key1"] 是怎么读取到 "Value1" 呢 ? 这就需要看源码了。 IConfigurationRoot到底是什么 , 以及 ["key1"] 的代码执行逻辑是怎样的 。

(注意: 为了方便了解主线逻辑 , 本文的中 .Net Core 源码有删减 )

2.2 IConfigurationRoot 相关源码

public interface IConfiguration
{
string this[string key] { get; set; }
} public interface IConfigurationRoot : IConfiguration
{
IEnumerable<IConfigurationProvider> Providers { get; }
} public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IConfigurationProvider> _providers; public IEnumerable<IConfigurationProvider> Providers => _providers; /// <summary>
/// 根据指定的Key获取或者设置对应的Value
/// </summary>
/// <param name="key">The configuration key.</param>
/// <returns>The configuration value.</returns>
public string this[string key]
{
get
{
for (var i = _providers.Count - 1; i >= 0; i--)
{
var provider = _providers[i]; if (provider.TryGet(key, out var value))
{
return value;
}
} return null;
}
set
{
if (!_providers.Any())
{
throw new InvalidOperationException(Resources.Error_NoSources);
} foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
}

1 通过上面的代码发现 ,IConfigurationRoot 有个 Providers 属性 ;

2 读取配置是通过ConfigurationRoot类中的索引器实现的 , 更具体的说就是通过 遍历Providers ,调用其.TryGet(key, out var value) 获取的 。

3 细心的同学会发现,一旦获取到配置就结束并返回了。那么就有了从哪先获取的问题 ,我们发现遍历时是LIFO(后入先出)的顺序,所以后面添加的Provider有更高的优先级 。

到目前为止 ,我们只需要记住一个主知识点 :获取配置是由多个 Provider(供应者) provide (供应) 的 ,并且越后面的 Provider 优先级越高,具有覆盖性。

再想深入就得研究一下 : IConfigurationProvider是怎么回事 , 特别是其中的 TryGet 方法是怎么个逻辑 。

2.3 IConfigurationProvider 相关源码

public interface IConfigurationProvider
{
bool TryGet(string key, out string value);
void Load();
} public abstract class ConfigurationProvider : IConfigurationProvider
{
/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; } /// <summary>
/// 根据指定的 键 获取对应的 值, 获取到返回true,否则返回 false
/// </summary>
public virtual bool TryGet(string key, out string value)
=> Data.TryGetValue(key, out value); /// <summary>
/// 加载数据
/// </summary>
public virtual void Load()
{
}
}

代码非常简单 ,每个Provider 维护了一个 Data (字典类型) 属性 , 配置就是从这个字典拿到的。 到此基本的读取配置逻辑我们已经知道了 ,给自己点个赞吧

2.4 使用便利性

这时 ,我们可以再看开头那个栗子,读取配置其实还有这种写法 :

configurationRoot.Providers.First().TryGet("Key1", out string Val);
Console.WriteLine("Key1=" + Val); //输出 Val=Value1

但是这种写法就感觉有些累赘了,还是 configurationRoot["Key1"] 更方便一些 , 有时我们甚至连"[" 和 "]" 都不想打 !

从使用的角度考虑,框架做了一些封装,并提供了不少扩展方法。但是基本的读取逻辑都是一样的

如果我们引入 "Microsoft.Extensions.Configuration.Binder" nuget包 ,读取配置操作时会发现一些新的方法。

比如配置和强模型绑定,并结合泛型使用 ,如:

public static T Get<T>(this IConfiguration configuration)

public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)

三 配置写入

现在我们需要找Provider中的Data 属性是怎么写入的 ,还从开头的控制台程序入手 , 其中有这样一段代码 :

new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = dicData }).Build();

我们重点看Add方法

3.1 IConfigurationBuilder 相关源码

public interface IConfigurationBuilder
{
IList<IConfigurationSource> Sources {get;} IConfigurationBuilder Add(IConfigurationSource source); IConfigurationRoot Build();
} /// <summary>
/// Used to build key/value based configuration settings for use in an application.
/// </summary>
public class ConfigurationBuilder : IConfigurationBuilder
{
/// <summary>
/// Returns the sources used to obtain configuration values.
/// </summary>
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); /// <summary>
/// Adds a new configuration source.
/// </summary>
/// <param name="source">The configuration source to add.</param>
/// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>
public IConfigurationBuilder Add(IConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(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>
/// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>
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);
}
}

Add就是把配置源 Source ,添加到内部的Sources列表。IConfigurationBuilder 除了Add方法还有一个 Build 方法。

IConfigurationBuilder 就像一个车间 ,Add一堆零件后 ,Build一下, 整出来一辆 汽车 (IConfigurationRoot) ,  很明显IConfigurationBuilder是一个构建者模式 。

我们继续探索 IConfigurationSource ,看 Build 方法的实现逻辑 .

3.2 IConfigurationSource 相关源码

    public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
} public class MemoryConfigurationSource : IConfigurationSource
{
/// <summary>
/// The initial key value configuration pairs.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; } /// <summary>
/// Builds the <see cref="MemoryConfigurationProvider"/> for this source.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
/// <returns>A <see cref="MemoryConfigurationProvider"/></returns>
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MemoryConfigurationProvider(this);
}
}

可以看到 ,IConfigurationSource 只干了一件事 :其 Build 方法返回一个Provider。

还拿开头的栗子来讲 ,MemoryConfigurationSource.Build 方法返回了 一个新的 MemoryConfigurationProvider 实例。

再看下 MemoryConfigurationProvider

 public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
{
private readonly MemoryConfigurationSource _source; /// <summary>
/// Initialize a new instance from the source.
/// </summary>
/// <param name="source">The source settings.</param>
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} _source = source; if (_source.InitialData != null)
{
foreach (var pair in _source.InitialData)
{
Data.Add(pair.Key, pair.Value);
}
}
} /// <summary>
/// Add a new key and value pair.
/// </summary>
/// <param name="key">The configuration key.</param>
/// <param name="value">The configuration value.</param>
public void Add(string key, string value)
{
Data.Add(key, value);
}
}

MemoryConfigurationProvider 的构造函数完成了数据的初始化 。

到此为止,开头的那个栗子我们就梳理完了。

也许是我们开头那个例子过于简单了 , 写配置这块有点虎头蛇尾的感觉 。

我们再看一下主角 IConfigurationProvider , 发现其中定义的 Load 方法 ,我们还知道 ConfigurationProvider 是一个虚拟类 ,其 Load 方法还是个虚方法。

我们在翻一下源码看有没有其它类继承它呢 ? 如果有的话 ,它们是不是覆盖了 Load 方法呢?

果然我们发现了 不少 Provider   , 如 JsonConfigurationProvider,XmlConfigurationProvider,EnvironmentVariablesConfigurationProvider 等。

为方便讲解,我们以EnvironmentVariablesConfigurationProvider 为例,并精简了代码。 Load 方法负责了负载配置的任务。男团其他成员也是如此。所以说 Provider们 实力担当。

public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
private readonly string _prefix; /// <summary>
/// Initializes a new instance.
/// </summary>
public EnvironmentVariablesConfigurationProvider() : this(string.Empty)
{ } /// <summary>
/// Initializes a new instance with the specified prefix.
/// </summary>
/// <param name="prefix">A prefix used to filter the environment variables.</param>
public EnvironmentVariablesConfigurationProvider(string prefix)
{
_prefix = prefix ?? string.Empty;
} /// <summary>
/// Loads the environment variables.
/// </summary>
public override void Load()
{
Load(Environment.GetEnvironmentVariables());
} internal void Load(IDictionary envVariables)
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var filteredEnvVariables = envVariables
.Cast<DictionaryEntry>()
.Where(entry => ((string)entry.Key).StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)); foreach (var envVariable in filteredEnvVariables)
{
var key = ((string)envVariable.Key).Substring(_prefix.Length);
data[key] = (string)envVariable.Value;
} Data = data;
}
}

但是 IConfigurationSource 呢 ,是不是可有可无 ?其实不是。IConfigurationSource 作用是提供原始配置,并将必要的信息传递给 Provider 。

我们现在大概知道 IConfigurationBuilder , IConfigurationProvider , IConfigurationRoot ,IConfigurationSource 之间的关系。我们总结一下,

1 各种配置原始数据( IConfigurationSource) 通过 IConfigurationProvider 转成键值对格式的配置数据

2 IConfigurationBuilder 内部维护多个 IConfigurationSource,IConfigurationBuilder 中的 Build方法 构建出 IConfigurationRoot

3 IConfigurationRoot 内部维护多个 IConfigurationProvider,并通过 IConfigurationProvider 读取配置数据

闯荡天下的天外流星全套剑谱奉上

限于篇幅 ,本篇介绍 配置读取配置写入 2个知识点。

下一篇会介绍 配置变更监控 ,自定义配置 2个知识点。

感觉写的还凑合就点个赞吧

.Net Core 配置源码学习 (一)的更多相关文章

  1. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  2. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  3. ASP.NET Core MVC 源码学习:详解 Action 的激活

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  4. ASP.NET Core MVC 源码学习:详解 Action 的匹配

    前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...

  5. 源码学习系列之SpringBoot自动配置(篇一)

    源码学习系列之SpringBoot自动配置源码学习(篇一) ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属 ...

  6. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  7. 【目录】Spring 源码学习

    [目录]Spring 源码学习 jwfy 关注 2018.01.31 19:57* 字数 896 阅读 152评论 0喜欢 9 用来记录自己学习spring源码的一些心得和体会以及相关功能的实现原理, ...

  8. Asp.NetCore源码学习[2-1]:配置[Configuration]

    Asp.NetCore源码学习[2-1]:配置[Configuration] 在Asp. NetCore中,配置系统支持不同的配置源(文件.环境变量等),虽然有多种的配置源,但是最终提供给系统使用的只 ...

  9. 源码学习系列之SpringBoot自动配置(篇二)

    源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...

  10. Vue2.0源码学习(4) - 合并配置

    合并配置 通过之前的源码学习,我们已经了解到了new Vue主要有两种场景,第一种就是在外部主动调用new Vue创建一个实例,第二个就是代码内部创建子组件的时候自行创建一个new Vue实例.但是无 ...

随机推荐

  1. css网页布局设置总结

    目录 1 网页样式 1.1 引入方法 1.1.1内联样式 1.1.2内部样式表 1.1.3链接外部样式 1.1.4导入外部样式 1.2 基础语法 1.3 选择器的分类 1.3.1标记选择器 1.3.2 ...

  2. 通过Shell脚本自动安装Hive&JDBC测试&提供CDH5网盘地址

    〇.参考地址 1.Linux下编写脚本自动安装hive https://blog.csdn.net/weixin_44911081/article/details/121227024?ops_requ ...

  3. Spring Boot+Mybatis:实现数据库登录注册与两种properties配置参数读取

    〇.参考资料 1.hutool介绍 https://blog.csdn.net/abst122/article/details/124091375 2.Spring Boot+Mybatis实现登录注 ...

  4. 【Java SE进阶】Day11 网络编程、TCP应用程序

    一.网络编程入门 1.软件架构 C/S:QQ.迅雷 B/S 共同点:都离不开网络的支持 网络编程:在一定的协议下,实现两台计算机通信 2.网络通信协议 通信协议:需遵守的规则,只有遵守才能通信 主要包 ...

  5. SourceGenerator 使用姿势(1):生成代理类,实现简单的AOP

    SourceGenerator 已经出来很久了,也一直在关注.之前观摩大佬 xljiulang 的 WebApiClient 使用 SourceGenerator 生成接口代理类,深受启发,准备拿过来 ...

  6. 推荐一款 在线+离线数据 同步框架 Dotmim.Sync

    移动智能应用可以分为在线模式.纯离线模式与"在线+离线"混合模式.在线模式下系统数据一般存储在服务器端的大中型数据库(如 SQL Server.Oracle.MySQL 等),移动 ...

  7. 自定义RBAC(5)

    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 把实体类及Service类都准备好了之后,就可以开始继续写业务代码了.Spring Security的强大之一就在于它的拦截器.那么这里也可以参 ...

  8. 教你铁威马NAS中如何进行阵列升级

    磁盘阵列 (RAID) 是磁盘阵列的管理工具.当TNAS 中安装的硬盘多于1个时,组建适当的磁盘阵列能提高硬盘的存储效率,提高数据的安全性. 磁盘阵列升级,比如,将原来是RAID 0 或者RAID 1 ...

  9. Win10下SDK Manager应用程序闪退问题的解决方法

    SDK Manager闪退原因:未找到Java的正确路径 解决办法: 1.在压缩包中找到Android.bat文件,右键编辑 2.打开的Android文件内容,找到如图的几行代码 将上面的代码替换成: ...

  10. 基于K-means聚类算法进行客户人群分析

    摘要:在本案例中,我们使用人工智能技术的聚类算法去分析超市购物中心客户的一些基本数据,把客户分成不同的群体,供营销团队参考并相应地制定营销策略. 本文分享自华为云社区<基于K-means聚类算法 ...