淺析.Net Core中Json配置的自動(dòng)更新
Pre
很早在看 Jesse 的 Asp.net Core快速入門 的課程的時(shí)候就了解到了在Asp .net core中,如果添加的Json配置被更改了,是支持自動(dòng)重載配置的,作為一名有著嚴(yán)重"造輪子"情節(jié)的程序員,最近在折騰一個(gè)博客系統(tǒng),也想造出一個(gè)這樣能自動(dòng)更新以Mysql為數(shù)據(jù)源的ConfigureSource,于是點(diǎn)開了AddJsonFile這個(gè)拓展函數(shù)的源碼,發(fā)現(xiàn)別有洞天,蠻有意思,本篇文章就簡(jiǎn)單地聊一聊Json config的ReloadOnChange是如何實(shí)現(xiàn)的,在學(xué)習(xí)ReloadOnChange的過程中,我們會(huì)把Configuration也順帶撩一把:grin:,希望對(duì)小伙伴們有所幫助.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration(option => { option.AddJsonFile("appsettings.json",optional:true,reloadOnChange:true); }) .UseStartup<Startup>();
在Asp .net core中如果配置了json數(shù)據(jù)源,把reloadOnChange屬性設(shè)置為true即可實(shí)現(xiàn)當(dāng)文件變更時(shí)自動(dòng)更新配置,這篇博客我們首先從它的源碼簡(jiǎn)單看一下,看完你可能還是會(huì)有點(diǎn)懵的,別慌,我會(huì)對(duì)這些代碼進(jìn)行精簡(jiǎn),做個(gè)簡(jiǎn)單的小例子,希望能對(duì)你有所幫助.
一窺源碼
AddJson
首先,我們當(dāng)然是從這個(gè)我們耳熟能詳?shù)臄U(kuò)展函數(shù)開始,它經(jīng)歷的演變過程如下.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,string path,bool optional,bool reloadOnChange) { return builder.AddJsonFile((IFileProvider) null, path, optional, reloadOnChange); }
傳遞一個(gè)null的FileProvider給另外一個(gè)重載Addjson函數(shù).
敲黑板,Null的FileProvider很重要,后面要考:smile:.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder,IFileProvider provider,string path,bool optional,bool reloadOnChange) { return builder.AddJsonFile((Action<JsonConfigurationSource>) (s => { s.FileProvider = provider; s.Path = path; s.Optional = optional; s.ReloadOnChange = reloadOnChange; s.ResolveFileProvider(); })); }
把傳入的參數(shù)演變成一個(gè)Action委托給 JsonConfigurationSource
的屬性賦值.
public static IConfigurationBuilder AddJsonFile(this IConfigurationBuilder builder, Action<JsonConfigurationSource> configureSource) { return builder.Add<JsonConfigurationSource>(configureSource); }
最終調(diào)用的builder.add
public static IConfigurationBuilder Add<TSource>(this IConfigurationBuilder builder,Action<TSource> configureSource)where TSource : IConfigurationSource, new() { TSource source = new TSource(); if (configureSource != null) configureSource(source); return builder.Add((IConfigurationSource) source); }
在Add方法里,創(chuàng)建了一個(gè)Source實(shí)例,也就是JsonConfigurationSource實(shí)例,然后把這個(gè)實(shí)例傳為剛剛的委托,這樣一來,我們?cè)谧钔饷鎮(zhèn)魅氲?"appsettings.json",optional:true,reloadOnChange:true
參數(shù)就作用到這個(gè)示例上了.
最終,這個(gè)實(shí)例添加到builder中.那么builder又是什么?它能干什么?
ConfigurationBuild
前面提及的builder默認(rèn)情況下是 ConfigurationBuilder
,我對(duì)它的進(jìn)行了簡(jiǎn)化,關(guān)鍵代碼如下.
public class ConfigurationBuilder : IConfigurationBuilder { public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); public IConfigurationBuilder Add(IConfigurationSource source) { Sources.Add(source); return this; } 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); } }
可以看到,這個(gè)builder中有個(gè)集合類型的Sources,這個(gè)Sources可以保存任何實(shí)現(xiàn)了 IConfigurationSource
的Source,前面聊到的 JsonConfigurationSource
就是實(shí)現(xiàn)了這個(gè)接口,常用的還有 MemoryConfigurationSource
, XmlConfigureSource
, CommandLineConfigurationSource
等.
另外,它有一個(gè)很重要的build方法,這個(gè)build方法在 WebHostBuilder
方法執(zhí)行 build
的時(shí)候也被調(diào)用,不要問我 WebHostBuilder.builder
方法什么執(zhí)行的:joy:.
public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); }
在ConfigureBuilder的方法里面就調(diào)用了每個(gè)Source的Builder方法,我們剛剛傳入的是一個(gè) JsonConfigurationSource
,所以我們有必要看看JsonSource的builder做了什么.
這里是不是被這些builder繞哭了? 別慌,下一篇文章中我會(huì)講解如何自定義一個(gè)ConfigureSoure,會(huì)把Congigure系列類UML類圖整理一下,應(yīng)該會(huì)清晰很多.
JsonConfigurationSource
public class JsonConfigurationSource : FileConfigurationSource { public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider(this); } }
這就是 JsonConfigurationSource
的所有代碼,未精簡(jiǎn),它只實(shí)現(xiàn)了一個(gè)Build方法,在Build內(nèi),EnsureDefaults被調(diào)用,可別小看它,之前那個(gè)空的FileProvider在這里被賦值了.
public void EnsureDefaults(IConfigurationBuilder builder) { FileProvider = FileProvider ?? builder.GetFileProvider(); } public static IFileProvider GetFileProvider(this IConfigurationBuilder builder) { return new PhysicalFileProvider(AppContext.BaseDirectory ?? string.Empty); }
可以看到這個(gè)FileProvider默認(rèn)情況下就是 PhysicalFileProvider
,為什么對(duì)這個(gè) FileProvider
如此寵幸讓我花如此大的伏筆要強(qiáng)調(diào)它呢?往下看.
JsonConfigurationProvider && FileConfigurationProvider
在JsonConfigurationSource的build方法內(nèi),返回的是一個(gè)JsonConfigurationProvider實(shí)例,所以直覺告訴我,在它的構(gòu)造函數(shù)內(nèi)必有貓膩:confused:.
public class JsonConfigurationProvider : FileConfigurationProvider { public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { } public override void Load(Stream stream) { try { Data = JsonConfigurationFileParser.Parse(stream); } catch (JsonReaderException e) { throw new FormatException(Resources.Error_JSONParseError, e); } } }
看不出什么的代碼,事出反常必有妖~~
看看base的構(gòu)造函數(shù).
public FileConfigurationProvider(FileConfigurationSource source) { Source = source; if (Source.ReloadOnChange && Source.FileProvider != null) { _changeTokenRegistration = ChangeToken.OnChange( () => Source.FileProvider.Watch(Source.Path), () => { Thread.Sleep(Source.ReloadDelay); Load(reload: true); }); } }
真是個(gè)天才,問題就在這個(gè)構(gòu)造函數(shù)里,它構(gòu)造函數(shù)調(diào)用了一個(gè) ChangeToken.OnChange
方法,這是實(shí)現(xiàn)ReloadOnChange的關(guān)鍵,如果你點(diǎn)到這里還沒有關(guān)掉,恭喜,好戲開始了.
ReloadOnChange
Talk is cheap. Show me the code (屁話少說,放 碼
過來).
public static class ChangeToken { public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer) { return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer); } }
OnChange方法里,先不管什么func,action,就看看這兩個(gè)參數(shù)的名稱,producer,consumer,生產(chǎn)者,消費(fèi)者,不知道看到這個(gè)關(guān)鍵詞想到的是什么,反正我想到的是小學(xué)時(shí)學(xué)習(xí)食物鏈時(shí)的:snake:與:rat:.
那么我們來看看這里的:snake:是什么,:rat:又是什么,還得回到 FileConfigurationProvider
的構(gòu)造函數(shù).
可以看到生產(chǎn)者:rat:是:
() => Source.FileProvider.Watch(Source.Path)
消費(fèi)者:snake:是:
() => { Thread.Sleep(Source.ReloadDelay); Load(reload: true); }
我們想一下,一旦有一條:rat:跑出來,就立馬被:snake:吃了,
那我們這里也一樣,一旦有FileProvider.Watch返回了什么東西,就會(huì)發(fā)生Load()事件來重新加載數(shù)據(jù).
:snake:與:rat:好理解,可是代碼就沒那么好理解了,我們通過 OnChange
的第一個(gè)參數(shù) Func<IChangeToken> changeTokenProducer
方法知道,這里的:rat:,其實(shí)是 IChangeToken
.
IChangeToken
public interface IChangeToken { bool HasChanged { get; } bool ActiveChangeCallbacks { get; } IDisposable RegisterChangeCallback(Action<object> callback, object state); }
IChangeToken的重點(diǎn)在于里面有個(gè)RegisterChangeCallback方法,:snake:吃:rat:的這件事,就發(fā)生在這回調(diào)方法里面.
我們來做個(gè):snake:吃:rat:的實(shí)驗(yàn).
實(shí)驗(yàn)1
static void Main() { //定義一個(gè)C:\Users\liuzh\MyBox\TestSpace目錄的FileProvider var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace"); //讓這個(gè)Provider開始監(jiān)聽這個(gè)目錄下的所有文件 var changeToken = phyFileProvider.Watch("*.*"); //注冊(cè)🐍吃🐀這件事到回調(diào)函數(shù) changeToken.RegisterChangeCallback(_=> { Console.WriteLine("老鼠被蛇吃"); }, new object()); //添加一個(gè)文件到目錄 AddFileToPath(); Console.ReadKey(); } static void AddFileToPath() { Console.WriteLine("老鼠出洞了"); File.Create("C:\\Users\\liuzh\\MyBox\\TestSpace\\老鼠出洞了.txt").Dispose(); }
這是運(yùn)行結(jié)果
可以看到,一旦在監(jiān)聽的目錄下創(chuàng)建文件,立即觸發(fā)了執(zhí)行回調(diào)函數(shù),但是如果我們繼續(xù)手動(dòng)地更改(復(fù)制)監(jiān)聽目錄中的文件,回調(diào)函數(shù)就不再執(zhí)行了.
這是因?yàn)閏hangeToken監(jiān)聽到文件變更并觸發(fā)回調(diào)函數(shù)后,這個(gè)changeToken的使命也就完成了,要想保持一直監(jiān)聽,那么我們就在在回調(diào)函數(shù)中重新獲取token,并給新的token的回調(diào)函數(shù)注冊(cè)通用的事件,這樣就能保持一直監(jiān)聽下去了.
這也就是ChangeToken.Onchange所作的事情,我們看一下源碼.
public static class ChangeToken { public static ChangeTokenRegistration<Action> OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer) { return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer); } } public class ChangeTokenRegistration<TAction> { private readonly Func<IChangeToken> _changeTokenProducer; private readonly Action<TAction> _changeTokenConsumer; private readonly TAction _state; public ChangeTokenRegistration(Func<IChangeToken> changeTokenProducer, Action<TAction> changeTokenConsumer, TAction state) { _changeTokenProducer = changeTokenProducer; _changeTokenConsumer = changeTokenConsumer; _state = state; var token = changeTokenProducer(); RegisterChangeTokenCallback(token); } private void RegisterChangeTokenCallback(IChangeToken token) { token.RegisterChangeCallback(_ => OnChangeTokenFired(), this); } private void OnChangeTokenFired() { var token = _changeTokenProducer(); try { _changeTokenConsumer(_state); } finally { // We always want to ensure the callback is registered RegisterChangeTokenCallback(token); } } }
簡(jiǎn)單來說,就是給token注冊(cè)了一個(gè) OnChangeTokenFired
的回調(diào)函數(shù),仔細(xì)看看 OnChangeTokenFired
里做了什么,總體來說三步.
1.獲取一個(gè)新的token.
2.調(diào)用消費(fèi)者進(jìn)行消費(fèi).
3.給新獲取的token再次注冊(cè)一個(gè)OnChangeTokenFired的回調(diào)函數(shù).
如此周而復(fù)始~~
實(shí)驗(yàn)2
既然知道了OnChange的工作方式,那么我們把實(shí)驗(yàn)1的代碼修改一下.
static void Main() { var phyFileProvider = new PhysicalFileProvider("C:\\Users\\liuzh\\MyBox\\TestSpace"); ChangeToken.OnChange(() => phyFileProvider.Watch("*.*"), () => { Console.WriteLine("老鼠被蛇吃"); }); Console.ReadKey(); }
執(zhí)行效果看一下
可以看到,只要被監(jiān)控的目錄發(fā)生了文件變化,不管是新建文件,還是修改了文件內(nèi)的內(nèi)容,都會(huì)觸發(fā)回調(diào)函數(shù),其實(shí)JsonConfig中,這個(gè)回調(diào)函數(shù)就是Load(),它負(fù)責(zé)重新加載數(shù)據(jù),可也就是為什么Asp .net core中如果把ReloadOnchang設(shè)置為true后,Json的配置一旦更新,配置就會(huì)自動(dòng)重載.
PhysicalFilesWatcher
那么,為什么文件一旦變化,就會(huì)觸發(fā)ChangeToken的回調(diào)函數(shù)呢? 其實(shí) PhysicalFileProvider
中調(diào)用了 PhysicalFilesWatcher
對(duì)文件系統(tǒng)進(jìn)行監(jiān)視,觀察PhysicalFilesWatcher的構(gòu)造函數(shù),可以看到 PhysicalFilesWatcher
需要傳入 FileSystemWatcher
, FileSystemWatcher
是 system.io
下的底層IO類,在構(gòu)造函數(shù)中給這個(gè)Watcher的Created,Changed,Renamed,Deleted注冊(cè)EventHandler事件,最終,在這些EventHandler中會(huì)調(diào)用ChangToken的回調(diào)函數(shù),所以文件系統(tǒng)一旦發(fā)生變更就會(huì)觸發(fā)回調(diào)函數(shù).
public PhysicalFilesWatcher(string root,FileSystemWatcher fileSystemWatcher,bool pollForChanges,ExclusionFilters filters) { this._root = root; this._fileWatcher = fileSystemWatcher; this._fileWatcher.IncludeSubdirectories = true; this._fileWatcher.Created += new FileSystemEventHandler(this.OnChanged); this._fileWatcher.Changed += new FileSystemEventHandler(this.OnChanged); this._fileWatcher.Renamed += new RenamedEventHandler(this.OnRenamed); this._fileWatcher.Deleted += new FileSystemEventHandler(this.OnChanged); this._fileWatcher.Error += new ErrorEventHandler(this.OnError); this.PollForChanges = pollForChanges; this._filters = filters; this.PollingChangeTokens = new ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>(); this._timerFactory = (Func<Timer>) (() => NonCapturingTimer.Create(new TimerCallback(PhysicalFilesWatcher.RaiseChangeEvents), (object) this.PollingChangeTokens, TimeSpan.Zero, PhysicalFilesWatcher.DefaultPollingInterval)); }
如果你和我一樣,對(duì)源碼感興趣,可以從官方的 aspnet/Extensions
中下載源碼研究: https://github.com/aspnet/Extensions
在下一篇文章中,我會(huì)講解如何自定義一個(gè)以Mysql為數(shù)據(jù)源的ConfigureSoure,并實(shí)現(xiàn)自動(dòng)更新功能,同時(shí)還會(huì)整理Configure相關(guān)類的UML類圖,有興趣的可以關(guān)注我以便第一時(shí)間收到下篇文章.
本文章涉及的代碼地址: https://github.com/liuzhenyulive/MiniConfiguration
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
WCF中使用nettcp協(xié)議進(jìn)行通訊的方法
這篇文章主要給大家介紹了關(guān)于WCF中使用nettcp協(xié)議進(jìn)行通訊的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用WCF具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07ASP.Net MVC_DotNetZip簡(jiǎn)單使用方法,解決文件壓縮的問題
下面小編就為大家?guī)硪黄狝SP.Net MVC_DotNetZip簡(jiǎn)單使用方法,解決文件壓縮的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06Global.asax取物理路徑/取絕對(duì)路徑具體方法
本文章來給大家簡(jiǎn)單介紹利用Global.asax取物理路徑和取絕對(duì)路徑代碼,有需要了解的朋友可參考參考2013-08-08RadioButtonList綁定圖片及泛型Dictionary應(yīng)用
讀取站點(diǎn)某一目錄的圖片,需要掌握LINQ與泛型Dictionary<TKey,TValue>的使用,本文將介紹RadioButtonList綁定圖片的實(shí)現(xiàn),感興趣的朋友可以了解下,或許對(duì)你有所幫助2013-02-02asp.net下模態(tài)對(duì)話框關(guān)閉之后繼續(xù)執(zhí)行服務(wù)器端代碼的問題
asp.net下模態(tài)對(duì)話框關(guān)閉之后繼續(xù)執(zhí)行服務(wù)器端代碼的問題...2007-04-04asp.net 數(shù)據(jù)類型轉(zhuǎn)換類代碼
asp.net 數(shù)據(jù)類型轉(zhuǎn)換類代碼,需要的朋友可以參考下2012-06-06asp.net中Session緩存與Cache緩存的區(qū)別分析
實(shí)現(xiàn)數(shù)據(jù)的緩存有很多種方法,有客戶端的Cookie,有服務(wù)器端的Session和Application2013-02-02Asp.net 文件上傳類(取得文件后綴名,保存文件,加入文字水印)
Asp.net 取得文件后綴名,保存文件,加入文字水印的代碼類2008-11-11