.Net Core Configuration源码探究
前言
????上篇文章我們演示了為Configuration添加Etcd數(shù)據(jù)源,并且了解到為Configuration擴(kuò)展自定義數(shù)據(jù)源還是非常簡單的,核心就是把數(shù)據(jù)源的數(shù)據(jù)按照一定的規(guī)則讀取到指定的字典里,這些都得益于微軟設(shè)計(jì)的合理性和便捷性。本篇文章我們將一起探究Configuration源碼,去了解Configuration到底是如何工作的。
ConfigurationBuilder
????相信使用了.Net Core或者看過.Net Core源碼的同學(xué)都非常清楚,.Net Core使用了大量的Builder模式許多核心操作都是是用來了Builder模式,微軟在.Net Core使用了許多在傳統(tǒng).Net框架上并未使用的設(shè)計(jì)模式,這也使得.Net Core使用更方便,代碼更合理。Configuration作為.Net Core的核心功能當(dāng)然也不例外。
????其實(shí)并沒有Configuration這個類,這只是我們對配置模塊的代名詞。其核心是IConfiguration接口,IConfiguration又是由IConfigurationBuilder構(gòu)建出來的,我們找到IConfigurationBuilder源碼大致定義如下
Add方法我們上篇文章曾使用過,就是為ConfigurationBuilder添加ConfigurationSource數(shù)據(jù)源,添加的數(shù)據(jù)源被存放在Sources這個屬性里。當(dāng)我們要使用IConfiguration的時候通過Build的方法得到IConfiguration實(shí)例,IConfigurationRoot接口是繼承自IConfiguration接口的,待會我們會探究這個接口。
我們找到IConfigurationBuilder的默認(rèn)實(shí)現(xiàn)類ConfigurationBuilder大致代碼實(shí)現(xiàn)如下
這個類的定義非常的簡單,相信大家都能看明白。其實(shí)整個IConfigurationBuilder的工作流程都非常簡單就是將IConfigurationSource添加到Sources中,然后通過Sources里的Provider去構(gòu)建IConfigurationRoot。
Configuration
通過上面我們了解到通過ConfigurationBuilder構(gòu)建出來的并非是直接實(shí)現(xiàn)IConfiguration的實(shí)現(xiàn)類而是另一個接口IConfigurationRoot
ConfigurationRoot
通過源代碼我們可以知道IConfigurationRoot是繼承自IConfiguration,具體定義關(guān)系如下
public interface IConfigurationRoot : IConfiguration {/// <summary>/// 強(qiáng)制刷新數(shù)據(jù)/// </summary>/// <returns></returns>void Reload();IEnumerable<IConfigurationProvider> Providers { get; } }public interface IConfiguration {string this[string key] { get; set; }/// <summary>/// 獲取指定名稱子數(shù)據(jù)節(jié)點(diǎn)/// </summary>/// <returns></returns>IConfigurationSection GetSection(string key);/// <summary>/// 獲取所有子數(shù)據(jù)節(jié)點(diǎn)/// </summary>/// <returns></returns>IEnumerable<IConfigurationSection> GetChildren();/// <summary>/// 獲取IChangeToken用于當(dāng)數(shù)據(jù)源有數(shù)據(jù)變化時,通知外部使用者/// </summary>/// <returns></returns>IChangeToken GetReloadToken(); }接下來我們查看IConfigurationRoot實(shí)現(xiàn)類ConfigurationRoot的大致實(shí)現(xiàn),代碼有刪減
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);//通過便利的方式調(diào)用ConfigurationProvider的Load方法,將數(shù)據(jù)加載到每個ConfigurationProvider的字典里foreach (var p in providers){p.Load();//監(jiān)聽每個ConfigurationProvider的ReloadToken實(shí)現(xiàn)如果數(shù)據(jù)源發(fā)生變化去刷新Token通知外部發(fā)生變化_changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));}}<summary>/// 讀取或設(shè)置配置相關(guān)信息/// </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{//這里的設(shè)置只是把值放到內(nèi)存中去,并不會持久化到相關(guān)數(shù)據(jù)源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>/// 手動調(diào)用該方法也可以實(shí)現(xiàn)強(qiáng)制刷新的效果/// </summary>public void Reload(){foreach (var provider in _providers){provider.Load();}RaiseChanged();}<summary>/// 強(qiáng)烈推薦不熟悉Interlocked的同學(xué)研究一下Interlocked具體用法/// </summary>private void RaiseChanged(){var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());previousToken.OnReload();} }上面展示了ConfigurationRoot的核心實(shí)現(xiàn)其實(shí)主要就是兩點(diǎn)
讀取的方式其實(shí)是循環(huán)匹配注冊進(jìn)來的每個provider里的數(shù)據(jù),是后來者居上的模式,同名key后注冊進(jìn)來的會先被讀取到,然后直接返回
構(gòu)造ConfigurationRoot的時候才把數(shù)據(jù)加載到內(nèi)存中,而且為注冊進(jìn)來的每個provider設(shè)置監(jiān)聽回調(diào)
ConfigurationSection
其實(shí)通過上面的代碼我們會產(chǎn)生一個疑問,獲取子節(jié)點(diǎn)數(shù)據(jù)返回的是另一個接口類型IConfigurationSection,我們來看下具體的定義
public interface IConfigurationSection : IConfiguration {string Key { get; }string Path { get; }string Value { get; set; } }這個接口也是繼承了IConfiguration,這就奇怪了分明只有一套配置IConfiguration,為什么還要區(qū)分IConfigurationRoot和IConfigurationSection呢?其實(shí)不難理解因?yàn)镃onfiguration可以同時承載許多不同的配置源,而IConfigurationRoot正是表示承載所有配置信息的根節(jié)點(diǎn),而配置又是可以表示層級化的一種結(jié)構(gòu),在根配置里獲取下來的子節(jié)點(diǎn)是可以表示承載一套相關(guān)配置的另一套系統(tǒng),所以單獨(dú)使用IConfigurationSection去表示,會顯得結(jié)構(gòu)更清晰,比如我們有如下的json數(shù)據(jù)格式
{"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是一個結(jié)構(gòu)化的存儲結(jié)構(gòu),其存儲元素分為三種一是簡單類型,二是對象類型,三是集合類型。但是字典是KV結(jié)構(gòu),并不存在結(jié)構(gòu)化關(guān)系,在.Net Corez中配置系統(tǒng)是這么解決的,比如以上信息存儲到字典中的結(jié)構(gòu)就是這種
| Key | Value |
| OrderId | 202005202220 |
| Address | 銀河系太陽系火星 |
| Products:0:Id | 1 |
| Products:0:Name | 果子貍 |
| Products:0:Detail:Color | 棕色 |
| Products:1:Id | 2 |
| Products:1:Name | 蝙蝠 |
| Products:1:Detail:Weight | 200g |
如果我想獲取Products節(jié)點(diǎn)下的第一條商品數(shù)據(jù)直接
IConfigurationSection productSection = configuration.GetSection("Products:0")類比到這里的話根配置IConfigurationRoot里存儲了訂單的所有數(shù)據(jù),獲取下來的子節(jié)點(diǎn)IConfigurationSection表示了訂單里第一個商品的信息,而這個商品也是一個完整的描述商品信息的數(shù)據(jù)系統(tǒng),所以這樣可以更清晰的區(qū)分Configuration的結(jié)構(gòu),我們來看一下ConfigurationSection的大致實(shí)現(xiàn)
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{//獲取當(dāng)前Section下的數(shù)據(jù)其實(shí)就是組合了Path和Keyreturn _root[ConfigurationPath.Combine(Path, key)];}set{_root[ConfigurationPath.Combine(Path, key)] = value;}}//獲取當(dāng)前節(jié)點(diǎn)下的某個子節(jié)點(diǎn)也是組合當(dāng)前的Path和子節(jié)點(diǎn)的標(biāo)識Keypublic IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));//獲取當(dāng)前節(jié)點(diǎn)下的所有子節(jié)點(diǎn)其實(shí)就是在字典里獲取包含當(dāng)前Path字符串的所有Keypublic IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);public IChangeToken GetReloadToken() => _root.GetReloadToken(); }這里我們可以看到既然有Key可以獲取字典里對應(yīng)的Value了,為何還需要Path?通過ConfigurationRoot里的代碼我們可以知道Path的初始值其實(shí)就是獲取ConfigurationSection的Key,說白了其實(shí)就是如何獲取到當(dāng)前IConfigurationSection的路徑。比如
//當(dāng)前productSection的Path是 Products:0 IConfigurationSection productSection = configuration.GetSection("Products:0"); //當(dāng)前productDetailSection的Path是 Products:0:Detail IConfigurationSection productDetailSection = productSection.GetSection("Detail"); //獲取到pColor的全路徑就是 Products:0:Detail:Color string pColor = productDetailSection["Color"];而獲取Section所有子節(jié)點(diǎn)GetChildrenImplementation來自于IConfigurationRoot的擴(kuò)展方法
internal static class InternalConfigurationRootExtensions {<summary>/// 其實(shí)就是在數(shù)據(jù)源字典里獲取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整體的思路有一定的了解,細(xì)節(jié)上的設(shè)計(jì)確實(shí)不少。但是整體實(shí)現(xiàn)思路還是比較清晰的。關(guān)于Configuration還有一個比較重要的擴(kuò)展方法就是將配置綁定到具體POCO的擴(kuò)展方法,該方法承載在ConfigurationBinder擴(kuò)展類了,由于實(shí)現(xiàn)比較復(fù)雜,也不是本篇文章的重點(diǎn),有興趣的同學(xué)可以自行查閱,這里就不做探究了。
總結(jié)
????通過以上部分的講解,其實(shí)我們可以大概的將Configuration配置相關(guān)總結(jié)為兩大核心抽象接口IConfigurationBuilder,IConfiguration,整體結(jié)構(gòu)關(guān)系可大致表示成如下關(guān)系
????配置相關(guān)的整體實(shí)現(xiàn)思路就是IConfigurationSource作為一種特定類型的數(shù)據(jù)源,它提供了提供當(dāng)前數(shù)據(jù)源的提供者ConfigurationProvider,Provider負(fù)責(zé)將數(shù)據(jù)源的數(shù)據(jù)按照一定的規(guī)則放入到字典里。IConfigurationSource添加到IConfigurationBuilder的容器中,后者使用Provide構(gòu)建出整個程序的根配置容器IConfigurationRoot。通過獲取IConfigurationRoot子節(jié)點(diǎn)得到IConfigurationSection負(fù)責(zé)維護(hù)子節(jié)點(diǎn)容器相關(guān)。這二者都繼承自IConfiguration,然后通過他們就可以獲取到整個配置體系的數(shù)據(jù)數(shù)據(jù)操作了。
????以上講解都是本人通過實(shí)踐和閱讀源碼得出的結(jié)論,可能會存在一定的偏差或理解上的誤區(qū),但是我還是想把我的理解分享給大家,希望大家能多多包涵。如果有大家有不同的見解或者更深的理解,可以在評論區(qū)多多留言。
????歡迎掃碼關(guān)注我的公眾號????
總結(jié)
以上是生活随笔為你收集整理的.Net Core Configuration源码探究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: angular 接入 IdentityS
- 下一篇: .NET Core HttpClient