.Net Core Configuration源码探究

前言

    上篇文章我们演示了为Configuration添加Etcd数据源,并且了解到为Configuration扩展自定义数据源还是非常简单的,核心就是把数据源的数据按照一定的规则读取到指定的字典里,这些都得益于微软设计的合理性和便捷性。本篇文章我们将一起探究Configuration源码,去了解Configuration到底是如何工作的。

ConfigurationBuilder

    相信使用了.Net Core或者看过.Net Core源码的同学都非常清楚,.Net Core使用了大量的Builder模式许多核心操作都是是用来了Builder模式,微软在.Net Core使用了许多在传统.Net框架上并未使用的设计模式,这也使得.Net Core使用更方便,代码更合理。Configuration作为.Net Core的核心功能当然也不例外。
    其实并没有Configuration这个类,这只是我们对配置模块的代名词。其核心是IConfiguration接口,IConfiguration又是由IConfigurationBuilder构建出来的,我们找到IConfigurationBuilder源码大致定义如下

public interface IConfigurationBuilder
{IDictionary<string, object> Properties { get; }IList<IConfigurationSource> Sources { get; }IConfigurationBuilder Add(IConfigurationSource source);IConfigurationRoot Build();
}

Add方法我们上篇文章曾使用过,就是为ConfigurationBuilder添加ConfigurationSource数据源,添加的数据源被存放在Sources这个属性里。当我们要使用IConfiguration的时候通过Build的方法得到IConfiguration实例,IConfigurationRoot接口是继承自IConfiguration接口的,待会我们会探究这个接口。
我们找到IConfigurationBuilder的默认实现类ConfigurationBuilder大致代码实现如下

public class ConfigurationBuilder : IConfigurationBuilder
{/// <summary>/// 添加的数据源被存放到了这里/// </summary>public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();/// <summary>/// 添加IConfigurationSource数据源/// </summary>/// <returns></returns>public IConfigurationBuilder Add(IConfigurationSource source){if (source == null){throw new ArgumentNullException(nameof(source));}Sources.Add(source);return this;}public IConfigurationRoot Build(){//获取所有添加的IConfigurationSource里的IConfigurationProvidervar providers = new List<IConfigurationProvider>();foreach (var source in Sources){var provider = source.Build(this);providers.Add(provider);}//用providers去实例化ConfigurationRootreturn new ConfigurationRoot(providers);}
}

这个类的定义非常的简单,相信大家都能看明白。其实整个IConfigurationBuilder的工作流程都非常简单就是将IConfigurationSource添加到Sources中,然后通过Sources里的Provider去构建IConfigurationRoot。

Configuration

通过上面我们了解到通过ConfigurationBuilder构建出来的并非是直接实现IConfiguration的实现类而是另一个接口IConfigurationRoot

ConfigurationRoot

通过源代码我们可以知道IConfigurationRoot是继承自IConfiguration,具体定义关系如下

public interface IConfigurationRoot : IConfiguration
{/// <summary>/// 强制刷新数据/// </summary>/// <returns></returns>void Reload();IEnumerable<IConfigurationProvider> Providers { get; }
}public interface IConfiguration
{string this[string key] { get; set; }/// <summary>/// 获取指定名称子数据节点/// </summary>/// <returns></returns>IConfigurationSection GetSection(string key);/// <summary>/// 获取所有子数据节点/// </summary>/// <returns></returns>IEnumerable<IConfigurationSection> GetChildren();/// <summary>/// 获取IChangeToken用于当数据源有数据变化时,通知外部使用者/// </summary>/// <returns></returns>IChangeToken GetReloadToken();
}

接下来我们查看IConfigurationRoot实现类ConfigurationRoot的大致实现,代码有删减

public class ConfigurationRoot : IConfigurationRoot, IDisposable
{private readonly IList<IIConfigurationProvider> _providers;private readonly IList<IDisposable> _changeTokenRegistrations;private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();public ConfigurationRoot(IList<IConfigurationProvider> providers){_providers = providers;_changeTokenRegistrations = new List<IDisposable>(providers.Count);//通过便利的方式调用ConfigurationProvider的Load方法,将数据加载到每个ConfigurationProvider的字典里foreach (var p in providers){p.Load();//监听每个ConfigurationProvider的ReloadToken实现如果数据源发生变化去刷新Token通知外部发生变化_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));}}<summary>/// 读取或设置配置相关信息/// </summary>public string this[string key]{get{//通过这个我们可以了解到读取的顺序取决于注册Source的顺序,采用的是后来者居上的方式//后注册的会先被读取到,如果读取到直接returnfor (var i = _providers.Count - 1; i >= 0; i--){var provider = _providers[i];if (provider.TryGet(key, out var value)){return value;}}return null;}set{//这里的设置只是把值放到内存中去,并不会持久化到相关数据源foreach (var provider in _providers){provider.Set(key, value);}}}public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);public IChangeToken GetReloadToken() => _changeToken;public IConfigurationSection GetSection(string key)=> new ConfigurationSection(this, key);<summary>/// 手动调用该方法也可以实现强制刷新的效果/// </summary>public void Reload(){foreach (var provider in _providers){provider.Load();}RaiseChanged();}<summary>/// 强烈推荐不熟悉Interlocked的同学研究一下Interlocked具体用法/// </summary>private void RaiseChanged(){var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());previousToken.OnReload();}
}

上面展示了ConfigurationRoot的核心实现其实主要就是两点

  • 读取的方式其实是循环匹配注册进来的每个provider里的数据,是后来者居上的模式,同名key后注册进来的会先被读取到,然后直接返回

  • 构造ConfigurationRoot的时候才把数据加载到内存中,而且为注册进来的每个provider设置监听回调

ConfigurationSection

其实通过上面的代码我们会产生一个疑问,获取子节点数据返回的是另一个接口类型IConfigurationSection,我们来看下具体的定义

public interface IConfigurationSection : IConfiguration
{string Key { get; }string Path { get; }string Value { get; set; }
}

这个接口也是继承了IConfiguration,这就奇怪了分明只有一套配置IConfiguration,为什么还要区分IConfigurationRoot和IConfigurationSection呢?其实不难理解因为Configuration可以同时承载许多不同的配置源,而IConfigurationRoot正是表示承载所有配置信息的根节点,而配置又是可以表示层级化的一种结构,在根配置里获取下来的子节点是可以表示承载一套相关配置的另一套系统,所以单独使用IConfigurationSection去表示,会显得结构更清晰,比如我们有如下的json数据格式

{"OrderId":"202005202220","Address":"银河系太阳系火星","Total":666.66,"Products":[{"Id":1,"Name":"果子狸","Price":66.6,"Detail":{"Color":"棕色","Weight":"1000g"}},{"Id":2,"Name":"蝙蝠","Price":55.5,"Detail":{"Color":"黑色","Weight":"200g"}}]
}

我们知道json是一个结构化的存储结构,其存储元素分为三种一是简单类型,二是对象类型,三是集合类型。但是字典是KV结构,并不存在结构化关系,在.Net Corez中配置系统是这么解决的,比如以上信息存储到字典中的结构就是这种

KeyValue
OrderId202005202220
Address银河系太阳系火星
Products:0:Id1
Products:0:Name果子狸
Products:0:Detail:Color棕色
Products:1:Id2
Products:1:Name蝙蝠
Products:1:Detail:Weight200g

如果我想获取Products节点下的第一条商品数据直接

IConfigurationSection productSection = configuration.GetSection("Products:0")

类比到这里的话根配置IConfigurationRoot里存储了订单的所有数据,获取下来的子节点IConfigurationSection表示了订单里第一个商品的信息,而这个商品也是一个完整的描述商品信息的数据系统,所以这样可以更清晰的区分Configuration的结构,我们来看一下ConfigurationSection的大致实现

public class ConfigurationSection : IConfigurationSection
{private readonly IConfigurationRoot _root;private readonly string _path;private string _key;public ConfigurationSection(IConfigurationRoot root, string path){_root = root;_path = path;}public string Path => _path;public string Key{get{return _key;}}public string Value{get{return _root[Path];}set{_root[Path] = value;}}public string this[string key]{get{//获取当前Section下的数据其实就是组合了Path和Keyreturn _root[ConfigurationPath.Combine(Path, key)];}set{_root[ConfigurationPath.Combine(Path, key)] = value;}}//获取当前节点下的某个子节点也是组合当前的Path和子节点的标识Keypublic IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));//获取当前节点下的所有子节点其实就是在字典里获取包含当前Path字符串的所有Keypublic IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);public IChangeToken GetReloadToken() => _root.GetReloadToken();
}

这里我们可以看到既然有Key可以获取字典里对应的Value了,为何还需要Path?通过ConfigurationRoot里的代码我们可以知道Path的初始值其实就是获取ConfigurationSection的Key,说白了其实就是如何获取到当前IConfigurationSection的路径。比如

//当前productSection的Path是 Products:0
IConfigurationSection productSection = configuration.GetSection("Products:0");
//当前productDetailSection的Path是 Products:0:Detail
IConfigurationSection productDetailSection = productSection.GetSection("Detail");
//获取到pColor的全路径就是 Products:0:Detail:Color
string pColor = productDetailSection["Color"];

而获取Section所有子节点GetChildrenImplementation来自于IConfigurationRoot的扩展方法

internal static class InternalConfigurationRootExtensions
{<summary>/// 其实就是在数据源字典里获取Key包含给定Path的所有值/// </summary>internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path){return root.Providers.Aggregate(Enumerable.Empty<string>(),(seed, source) => source.GetChildKeys(seed, path)).Distinct(StringComparer.OrdinalIgnoreCase).Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));}
}

相信讲到这里,大家对ConfigurationSection或者是对Configuration整体的思路有一定的了解,细节上的设计确实不少。但是整体实现思路还是比较清晰的。关于Configuration还有一个比较重要的扩展方法就是将配置绑定到具体POCO的扩展方法,该方法承载在ConfigurationBinder扩展类了,由于实现比较复杂,也不是本篇文章的重点,有兴趣的同学可以自行查阅,这里就不做探究了。

总结

    通过以上部分的讲解,其实我们可以大概的将Configuration配置相关总结为两大核心抽象接口IConfigurationBuilder,IConfiguration,整体结构关系可大致表示成如下关系

    配置相关的整体实现思路就是IConfigurationSource作为一种特定类型的数据源,它提供了提供当前数据源的提供者ConfigurationProvider,Provider负责将数据源的数据按照一定的规则放入到字典里。IConfigurationSource添加到IConfigurationBuilder的容器中,后者使用Provide构建出整个程序的根配置容器IConfigurationRoot。通过获取IConfigurationRoot子节点得到IConfigurationSection负责维护子节点容器相关。这二者都继承自IConfiguration,然后通过他们就可以获取到整个配置体系的数据数据操作了。
    以上讲解都是本人通过实践和阅读源码得出的结论,可能会存在一定的偏差或理解上的误区,但是我还是想把我的理解分享给大家,希望大家能多多包涵。如果有大家有不同的见解或者更深的理解,可以在评论区多多留言。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试官:你说你喜欢研究新技术,那么请说说你对 Blazor 的了解

阅读本文大概需要 1.5 分钟。最近在几个微信 .NET 交流群里大家讨论比较频繁的话题就是这几天自己的面试经历。面试官&#xff1a;“你刚说你喜欢研究新技术&#xff0c;那么你对 Blazor 了解多少&#xff1f;”作为一位专注于 .NET 开发的软件工程师&#xff0c;你好意思说你对…

Java变量

变量 ​ 变量是程序的基本组成单位 变量的介绍 概念 变量相当于内存中一个数据存储空间的表示&#xff0c;你可以把变量看做是一个房间的门牌号&#xff0c;通过门牌号我们可以找到房间&#xff0c;而通过变量名可以访问到变量(值)。 01&#xff1a; class Test {public s…

[Student.Achieve] 学生教务管理系统开源

&#xff08;源自&#xff1a;https://neters.club&#xff09;一觉醒来Github改版了&#xff0c;我个人还是挺喜欢的????。还有两个月就是老张做开源两周年了&#xff0c;时间真快&#xff0c;也慢慢的贡献了很多的开源作品&#xff0c;上边的是主要的七个作品&#xff0c…

.NET Core HttpClient源码探究

前言在之前的文章我们介绍过HttpClient相关的服务发现&#xff0c;确实HttpClient是目前.NET Core进行Http网络编程的的主要手段。在之前的介绍中也看到了&#xff0c;我们使用了一个很重要的抽象HttpMessageHandler&#xff0c;接下来我们就探究一下HttpClient源码&#xff0c…

Java 多线程:线程优先级

1 优先级取值范围 Java 线程优先级使用 1 ~ 10 的整数表示&#xff1a; 最低优先级 1&#xff1a;Thread.MIN_PRIORITY 最高优先级 10&#xff1a;Thread.MAX_PRIORITY 普通优先级 5&#xff1a;Thread.NORM_PRIORITY 2 获取线程优先级 public static void main(String[]…

《Unit Testing》1.1 -1.2 单元测试的目的

本系列是《Unit Testing》 一书的读书笔记 精华提取。书中的例子 C# 语言编写&#xff0c;但概念是通用的&#xff0c;只要懂得面向对象编程就可以。 单元测试当前的状态目前&#xff0c;在&#xff08;美国的&#xff09;大部分公司里&#xff0c;单元测试都是强制性的。生产…

Java Exception

Exception 异常捕获 将代码块选中->ctrlaltt->选中try-catch 01: public class Exception01 {public static void main(String[] args) {int n1 10;int n2 0;try {int res n1/n2;} catch (Exception e) { // e.printStackTrace();System.out.println(e.…

《Unit Testing》1.3 使用覆盖率指标来度量测试套件的好坏

使用覆盖率来度量测试套件&#xff08;Test Suite&#xff09;的质量有两种比较流行的测试覆盖率的度量方法&#xff1a;代码覆盖率分支覆盖率覆盖率度量会显示一个测试套件&#xff08;Test Suite&#xff09;会执行多少代码&#xff0c;范围从 0 至 100%。除了上述两种方法之…

Linux创始人:v5.8是有史以来最大的发行版之一

导语Linux v5.8已经修改了所有文件的20&#xff05;&#xff0c;是迄今为止变化最大的一次发行版。正文Linux创始人Linus Torvalds表示&#xff1a;Linux内核5.8版是“我们有史以来最大的发行版之一”。如果一切顺利&#xff0c;Linux v5.8稳定版应该在2020年8月的某个时候出现…

[高等数学]这你不背?

求导及求微分的基本公式: 泰勒中值定理: 麦克劳林公式: 不定积分公式: 凑微分: 第二类换元积分法常用的三种情况: 求高阶导数的几个公式: 二阶常系数非齐次线性微分方程的特解: 排列组合公式: C的计算&#xff1a; 下标的数字乘以上标的数字的个数,且每个数字都要-1.再除以上标…

怎么开会才不浪费时间?

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「148」篇原创敬上大家好&#xff0c;我是Z哥&#xff0c;先祝大家端午节日快乐。节日期间就发篇比较短的文章吧。人在职场混&#xff0c;开会应该是本职工作之外花…

.NET 5.0预览版6发布:支持Windows ARM64设备

2020年6月25日&#xff0c;微软dotnet团队在博客宣布了第六个 .NET 5.0 的预览版&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-6/&#xff0c;在改进性能的同时增加了一些新的功能。ASP.NET Core和 EF Core也将于今日发布了。注意&#xff1…

利用真值表法求取主析取范式以及主合取范式的实现(C++)

代码如下: #include <iostream> #include <stack> #include <string> #include <vector> using namespace std; const int N 300; stack<char> s; stack<char> v; int seq; bool vis[N]; bool flag[N]; void dfs(int n); vector<int&…

基于 Blazor 开发五子棋小游戏

今天是农历五月初五&#xff0c;端午节。在此&#xff0c;祝大家端午安康&#xff01;端午节是中华民族古老的传统节日之一。端午也称端五&#xff0c;端阳。此外&#xff0c;端午节还有许多别称&#xff0c;如&#xff1a;午日节、重五节、五月节、浴兰节、女儿节、天中节、地…

汇编cmp比较指令详解

刚刚看到了cmp指令&#xff0c;一开始有点晕。后来上网找了些资料&#xff0c;终于看明白了&#xff0c;为了方便初学者&#xff0c;我就简单写下我的思路吧。高手绕过&#xff0c;谢谢&#xff01; cmp(compare)指令进行比较两个操作数的大小例:cmp oprd1,oprd2为第一个操作减…

如何在ASP.NET Core中使用SignalR构建与Angular通信的实时通信应用程序

图片假设我们要创建一个监视Web应用程序&#xff0c;该应用程序为用户提供了一个能够显示一系列信息的仪表板&#xff0c;这些信息会随着时间的推移而更新。第一种方法是在定义的时间间隔&#xff08;轮询&#xff09;定期调用API 以更新仪表板上的数据。无论如何&#xff0c;还…

LED计数电路,5输入按键编码器,7段数码管显示驱动集成为LED计数测试电路

LED计数电路: 5输入按键编码器: 7段数码管显示驱动真值表: 集成:

越卖越涨?腾讯股票3月后大涨45%,超越“阿里”成中国第一,市值相当于14.3个百度!...

01 腾讯股价大涨据股市最新消息&#xff1a;腾讯股价已连续3个交易日上涨, 其中6月22日腾讯股价重返470港元关口&#xff0c;公司市值突破4.5万亿港元&#xff0c;折合4.0万亿人民币&#xff1b;而6月23日上午腾讯股价再度大涨4.05%&#xff0c;刷出493.8港元的新高&#xf…

4位无符号比较器设计

4位比较器原理&#xff1a; 4位比较 a3a2a1a0 : b3b2b1b0&#xff0c;比较顺序从高位到低位&#xff0c;当高位大、小关系确定时则无需看低位&#xff0c;当高位相等时再看相邻低位的关系。 注意&#xff1a;对于三个比较结果&#xff0c;已知其中任意两个&#xff0c;可以用…