本節所謂的“配置同步”主要體現在兩個方面:其一,如何監控配置源併在其變化的時候自動載入其數據,其目的是讓應用中通過Configuration對象承載的配置與配置源的數據同步;其二、當Configuration對象承載的配置放生變換的時候如何嚮應用程式發送通知,最終讓應用程式使用最新的配置。 一、配置 ...
本節所謂的“配置同步”主要體現在兩個方面:其一,如何監控配置源併在其變化的時候自動載入其數據,其目的是讓應用中通過Configuration對象承載的配置與配置源的數據同步;其二、當Configuration對象承載的配置放生變換的時候如何嚮應用程式發送通知,最終讓應用程式使用最新的配置。
一、配置與配置源的同步
配置模型提供了三個原生ConfigurationProvider(JsonConfigrationProvider、XmlConfigurationProvider和IniConfigurationProvider)使我們可以將三種格式(JSON、XML和INI)的文件作為配置原始數據的來源,所以針對物理文件的配置同步是配置同步機制的一個主要的應用領域。在上面演示的實例中,基於物理文件的同步是通過調用ConfigurationRoot的擴展方法ReloadOnChanged來實現的。
這個擴展方法定義在NuGet包“Microsoft.Extensions.Configuration.FileProviderExtensions”之中,除了在我們演示的實例中使用的那個方法之外,這個ReloadOnChanged方法還具有如下兩個額外的重載。對於這三個ReloadOnChanged方法重載來說,最終的實現均落在第三個重載上。至於最本質的物理文件監控的功能則由一個名為FileProvider的對象負責。
1: public static class FileProviderExtensions
2: {
3: public static IConfigurationRoot ReloadOnChanged(
4: this IConfigurationRoot config, string filename);
5:
6: public static IConfigurationRoot ReloadOnChanged(
7: this IConfigurationRoot config, string basePath, string filename);
8:
9: public static IConfigurationRoot ReloadOnChanged(this IConfigurationRoot config,
10: IFileProvider fileProvider, string filename);
11: }
這裡所謂的FileProvider是對所有實現了IFileProvider介面的類型及其對象的統稱。IFileProvier介面定義在命名空間“Microsoft.AspNet.FileProviders”下,它通過定義其中的方法提供抽象化的目錄與文件信息,針對文件監控相關的方法也定義在這個介面下。如下麵的代碼片段所示,IFileProvier具有三個方法,其中GetDirectoryContents和GetFileInfo用於提供目錄和文件的相關信息,我們只需要關註旨在監控文件變化的Watch方法。
1: public interface IFileProvider
2: {
3: IDirectoryContents GetDirectoryContents(string subpath);
4: IFileInfo GetFileInfo(string subpath);
5: IChangeToken Watch(string filter);
6: }
一個FileProvider總是針對一個具體的目錄,Watch方法的參數filter旨在幫助篩選出需要監控的文件。這個參數是一個可以攜帶通配符(“*”)的字元串,比如 “ *.*”則表示所有文件,而“ *.json”則表示所有擴展名為“ .json”的文件。如果我們需要監控當前目錄下某個確定的文件,直接將文件名作為參數即可。Watch方法的返回類型為具有如下定義的IChangeToken介面,我們可以將它理解為一個用於傳遞數據變換通知的令牌。
1: public interface IChangeToken
2: {
3: bool HasChanged { get; }
4: bool ActiveChangeCallbacks { get; }
5:
6: IDisposable RegisterChangeCallback(Action<object> callback, object state);
7: }
IChangeToken的只讀屬性HasChanged表示目標數據是否發生改變。我們可以通過調用它的RegisterChangeCallback方法註冊一個在數據發生變化時需要執行的回調操作。該方法返回的對象對應的類型必須實現IDisposable介面,回調註冊的接觸可以通過Dispose方法來完成。至於IChangeToken介面的另個只讀屬性ActiveChangeCallbacks表示當數據發生變化時是否需要主動執行註冊的回調操作。實際上IConfiguration的GetReloadToke方法的返回類型就是這麼一個介面,至於該方法具體返回一個怎樣的對象,我們會在下一節予以介紹。
當我們指定一個具體的FileProvider對象調用ConfigurationRoot的擴展方法ReloadOnChanged時,後者會調用這個FileProvider的RegisterChangeCallback方法以註冊一個在指定文件發生變化時的回調。至於這個註冊的回調,它會調用ConfigurationRoot的Reload方法實現對配置數據的重新載入。由於註冊了這樣一個回調,該方法只需要調用FileProvider的Watch方法監控指定文件的變化即可,如下所示的代碼片段基本上體現了ReloadOnChanged方法的邏輯。
1: public static IConfigurationRoot ReloadOnChanged(
2: this IConfigurationRoot config, IFileProvider fileProvider, string filename)
3: {
4: Action<object> callback = null;
5: callback = _ =>
6: {
7: config.Reload();
8: fileProvider.Watch(filename).RegisterChangeCallback(callback, null);
9: };
10: fileProvider.Watch(filename).RegisterChangeCallback(callback, null);
11: return config;
12: }
如果我們通過指定目錄和文件名調用另一個ReloadOnChanged方法重載,後者會根據指定的目錄創建一個PhysicalFileProvider對象並作為參數調用上面這個重載。顧名思義,PhysicalFileProvider是一個針對具體物理文件的FileProvider,它實際上是藉助一個FileSystemWatcher對象來監控指定的文件。這個ReloadOnChanged方法的實現邏輯體現在如下所示的代碼片段中。當我們僅僅指定監控文件名調用第一個ReloadOnChanged方法重載時,該方法會將當前應用所在的目錄作為參數調用上面一個重載。
1: public static class FileProviderExtensions
2: {
3: public static IConfigurationRoot ReloadOnChanged(
4: this IConfigurationRoot config, string basePath, string filename)
5: => config.ReloadOnChanged(new PhysicalFileProvider(basePath), filename);
6: //其他成員
7: }
二、應用重新載入的配置
ConfigurationRoot通過擴展方法ReloadOnChanged方法與一個具體的物理文件綁定在一起,針對該文件的任何修改操作都會促使Reload方法的調用,進而保證自身承載的數據總是與配置源保持同步。現在我們來討論配置同步的另一個話題,即如何在不重啟應用程式的情況下使用新的配置。要瞭解這個問題的解決方案,我們得先來聊聊定義在IConfiguration介面中這個一直刻意迴避的方法GetReloadToken。
1: public interface IConfiguration
2: {
3: //其他成員
4: IChangeToken GetReloadToken();
5: }
如上面的代碼片段所示,這個GetReloadToken方法的返回類型為上面討論過的IChangeToken介面,我們說可以將後者視為一個傳遞數據變化信息的令牌。對於一個Configuration對象來說,它所謂的數據變換體現作為配置根節點的ConfigurationRoot對象的重新載入,所以這個方法返回的ChangeToken對象體現了最近一次載入引起的配置變化。
1: public class ConfigurationReloadToken : IChangeToken
2: {
3: public void OnReload();
4: public IDisposable RegisterChangeCallback(Action<object> callback,
5: object state);
6:
7: public bool ActiveChangeCallbacks { get; }
8: public bool HasChanged { get; }
9: }
對於實現了IConfiguration介面的兩個預設類型(ConfigurationRoot和ConfigurationSection)來說,它們的GetReloadToken方法返回的是一個ConfigurationReloadToken對象。如上面的代碼片段所示,除了實現定義在IConfiguration介面中的所有成員之外,ConfigurationReloadToken還具有另一個名為OnReload的方法。當配置數據發生變化,也就是調用通過ConfigurationRoot的Reload方法重新載入配置的時候,這個方法會被調用用以發送“配置已經發生變化”的信號。
實現在ConfigurationReloadToken之中用於傳遞配置變化的邏輯其實很簡單,具體的邏輯是藉助於一個CancellationTokenSource對象來完成。如果讀者朋友們瞭解針對Task的非同步編程,相信對這個類型不會感到陌生。總的來說,我們可以利用CancellationTokenSource向某個非同步執行的Task發送“取消任務”的信號。
1: public class ConfigurationReloadToken : IChangeToken
2: {
3: private CancellationTokenSource tokenSource = new CancellationTokenSource();
4:
5: public void OnReload() => tokenSource.Cancel();
6: public IDisposable RegisterChangeCallback(Action<object> callback, object state)
7: => tokenSource.Token.Register(callback, state);
8:
9: public bool ActiveChangeCallbacks { get; } = true;
10: public bool HasChanged
11: {
12: get { return tokenSource.IsCancellationRequested; }
13: }
14: }
如上面的代碼片段所示,ConfigurationReloadToken本質上就是一個CancellationTokenSource對象的封裝。當OnReload方法被調用的時候,它直接調用CancellationTokenSource的Cancel方法發送取消任務的請求,而HasChanged屬性則通過CancellationTokenSource的IsCancellationRequested屬性通過判斷任務取消請求是否發出來判斷配置數據是否發生變化。通過RegisterChangeCallback註冊的回調最終註冊到由CancellationTokenSource創建的CancellationToken對象上,所以一旦OnReload方法被調用,註冊的回調會自動執行。ConfigurationReloadToken的ActiveChangeCallbacks屬性總是返回True。
ConfigurationRoot和ConfigurationSection這兩個類型分別採用如下的形式實現了GetReloadToken方法。我們從給出的代碼片段不難看出所有的ConfigurationSection對象和作為它們根的ConfigurationRoot對象來說,它們的GetReloadToken方法在同一時刻返回的是同一個ConfigurationReloadToken對象。當ConfigurationRoot的Reload方法被調用的時候,當前ConfigurationReloadToken對象的OnReload方法會被調用,在此之後一個新的ConfigurationReloadToken對象會被創建出來並代替原來的對象。
1: public class ConfigurationRoot : IConfigurationRoot
2: {
3: private ConfigurationReloadToken reloadToken = new ConfigurationReloadToken();
4:
5: public IChangeToken GetReloadToken()
6: {
7: return reloadToken;
8: }
9:
10: public void Reload()
11: {
12: //省略重新載入配置代碼
13: Interlocked.Exchange<ConfigurationReloadToken>(ref this._reloadToken,
14: new ConfigurationReloadToken()).OnReload();
15: }
16: //其他成員
17: }
18:
19: public class ConfigurationSection : IConfigurationSection, IConfiguration
20: {
21: private readonly ConfigurationRoot root;
22: public IChangeToken GetReloadToken()
23: {
24: return root.GetReloadToken();
25: }
26: //其他成員
27: }
正是因為GetReloadToken方法並不能保證每次返回的都是同一個ConfigurationReloadToken對象,所以當我們註冊配置載入回調時,需要在回調中完成針對新的ConfigurationReloadToken對象的回調註冊,實際上我們上面演示的實例就是這麼做的。除此之外,調用RegisterChangeCallback方法會返回一個類型實現了IDisposable 介面的對象,不要忘記調用它的Dispose方法以免產生一些記憶體泄漏的問題。
1: public class Program
2: {
3: private static IDisposable callbackRegistration;
4: private static void OnSettingChanged(object state)
5: {
6: callbackRegistration?.Dispose();
7: IConfiguration configuration = (IConfiguration)state;
8: Console.WriteLine(configuration.Get<ThreadPoolSettings>());
9: callbackRegistration = configuration.GetReloadToken()
10: .RegisterChangeCallback(OnSettingChanged, state);
11: }
12: }
ASP.NET Core的配置(1):讀取配置信息
ASP.NET Core的配置(2):配置模型詳解
ASP.NET Core的配置(3): 將配置綁定為對象[上篇]
ASP.NET Core的配置(3): 將配置綁定為對象[下篇]
ASP.NET Core的配置(4):多樣性的配置源[上篇]
ASP.NET Core的配置(4):多樣性的配置源[中篇]
ASP.NET Core的配置(4):多樣性的配置源[下篇]
ASP.NET Core的配置(5):配置的同步[上篇]
ASP.NET Core的配置(5):配置的同步[下篇]
參考頁面:http://qingqingquege.cnblogs.com/p/5933752.html