Net6Configuration & Options 源碼分析 Part3 IOptionsMonitor 是如何接收到配置文件變更並同步數據源的

来源:https://www.cnblogs.com/hts92/archive/2022/03/22/16012929.html
-Advertisement-
Play Games

配置源的同步 IOptionsMonitor 使用 //以下demo演示使用IOptionsMonitor重新載入配置並當重新載入配置是執行回調函數 var configuration = new ConfigurationBuilder().AddJsonFile(path: "profile.j ...


配置源的同步 IOptionsMonitor 使用

//以下demo演示使用IOptionsMonitor重新加载配置并当重新加载配置是执行回调函数

var configuration = new ConfigurationBuilder().AddJsonFile(path: "profile.json",
                                                           optional: false,
                                                           reloadOnChange: true).Build();
new ServiceCollection().AddOptions().Configure<Profile>(configuration).BuildServiceProvider().GetRequiredService<IOptionsMonitor<Profile>>().OnChange(profile => Console.WriteLine($"data reload: {profile.Age}"));
Console.Read();

public class Profile
{
    public int Age { get; set; }
}

配置源的同步 IOptionsMonitor 源码分析

当文件变更时如何向外发送通知的以及 Reload data。

以JsonConfiguration为例:
FileConfigurationProvider通过FileProvider.Watch当文件发生改变的时候会调用Load,load方法做了两件事情,1.调用子类同名虚方完成具体数据的reload data(由具体实现类:JsonConfigurationProvider)2。提供调用OnReload(由父类ConfigurationProvider实现)。完成对外发送data change的通知。OnReload内调用了_reloadToken.OnReload发送回调通知并产生一个新的ConfigurationReloadToken重新赋值给_reloadToken,通知注册到FileConfigurationProvider._reloadToken的回调,那么想接收到文件改变的消息只需要通过GetReloadToken()得到_reloadToken属性并将回调函数注册进去即可。
如下是此三个类的继承关系JsonConfiguration->FileConfigurationProvider->ConfigurationProvider
知道了这些在看下ConfigurationRoot。

public abstract class FileConfigurationProvider : ConfigurationProvider, IDisposable
{
    public FileConfigurationProvider(FileConfigurationSource source!!)
    {
        Source = source;
        if (Source.ReloadOnChange && Source.FileProvider != null)
        {
            _changeTokenRegistration = ChangeToken.OnChange(
                () => Source.FileProvider.Watch(Source.Path!),
                () =>
                {
                    // 重新从JsonFile Load 数据并
                    Load(reload: true);
                });
        }
    }

    private void Load(bool reload)
    {
        IFileInfo? file = Source.FileProvider?.GetFileInfo(Source.Path ?? string.Empty);
        using Stream stream = OpenRead(file);
        try
        {
            // 此处调用具体实现类的Load 方法例如JsonConfigurationProvider
            Load(stream);
        }
       
        // 发送OnReload 并重新生成ConfigurationReloadToken共下次使用。
        OnReload();
    }
}

public class JsonConfigurationProvider : FileConfigurationProvider
{
    public JsonConfigurationProvider(JsonConfigurationSource source) : base(source) { }

    public override void Load(Stream stream)
    {
        Data = JsonConfigurationFileParser.Parse(stream);
    }
}

public abstract class ConfigurationProvider : IConfigurationProvider
{
    protected void OnReload()
    {
        ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }

    public IChangeToken GetReloadToken()
    {
        return _reloadToken;
    }
}

ConfigurationRoot会循环调用把所有的providers 并通过IConfigurationProvider.GetReloadToken()得到FileConfigurationProvider._reloadToken,然后注册上RaiseChanged作为回调函数。以文件系统为例,当文件发生改动时会调用此回调函数,此回调函数又会调用ConfigurationRoot的_changeToken.OnReload()向外发送通知。
ConfigurationChangeTokenSource:注册的时机为ConfigurationChangeTokenSource.Configure.

我们作为使用者注册的回调事件就是注册在OptionsMonitor._onChange中。当用户使用OptionsMonitor时,其在构造方法通过DI拿到使用ConfigurationChangeTokenSource作为包装类,其包装的是ConfigurationRoot._changeToken,并把自身的事件OptionsMonitor._onChange作为回调函数注册在包装类ConfigurationChangeTokenSource.包装的ConfigurationRoot._changeToken中。自此完成了整个回调链条。

// ConfigurationRoot向IConfigurationProvider注册回调函数拼接回调链条。
public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
    _providers = providers;
    _changeTokenRegistrations = new List<IDisposable>(providers.Count);
    foreach (IConfigurationProvider p in providers)
    {
        p.Load();
        // 回调链条拼接
        _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
    }

    private void RaiseChanged()
    {
        ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }
}

// ConfigurationChangeTokenSource 包装类与注册 OptionsConfigurationServiceCollectionExtensions
public class ConfigurationChangeTokenSource<TOptions> : IOptionsChangeTokenSource<TOptions>
{
    private IConfiguration _config;

    public ConfigurationChangeTokenSource(IConfiguration config) : this(Options.DefaultName, config){}

    public IChangeToken GetChangeToken()
    {
        return _config.GetReloadToken();
    }
}

public static class OptionsConfigurationServiceCollectionExtensions
{
     public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services!!, string? name, IConfiguration config!!, Action<BinderOptions>? configureBinder) where TOptions : class
    {
        services.AddOptions();
        services.AddSingleton<IOptionsChangeTokenSource<TOptions>>(new ConfigurationChangeTokenSource<TOptions>(name, config));
        return services.AddSingleton<IConfigureOptions<TOptions>>(new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder));
    }
}

public class OptionsMonitor<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> : IOptionsMonitor<TOptions>, IDisposable
    where TOptions : class
{
    internal event Action<TOptions, string>? _onChange;
    public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
    {
        ChangeToken.OnChange(
                          () => source.GetChangeToken(),
                          (name) => InvokeChanged(name),
                          source.Name);
        private void InvokeChanged(string? name)
        {
            name = name ?? Options.DefaultName;
            _cache.TryRemove(name);
            TOptions options = Get(name);
            if (_onChange != null)
            {
                _onChange.Invoke(options, name);
            }
        }
 
    }
     public IDisposable OnChange(Action<TOptions, string> listener)
    {
        var disposable = new ChangeTrackerDisposable(this, listener);
        _onChange += disposable.OnChange;
        return disposable;
    }
}


总结

整个过程回调使用了两个ConfigurationReloadToken分别是。1. FileConfigurationProvider提供了一个ConfigurationReloadToken 2.提供了一个ConfigurationRoot._changeToken 。回调链条的拼接是。1.FileConfigurationProvider构造函数中文件的Watch与FileConfigurationProvider._reloadToken同时在这里也完成了数据的reload data 2 ConfigurationRoot的构造函数中与IConfigurationProvider._reloadToken进行的回调链条拼接 。第三次拼接是把用户注册的回调函注册在OptionsMonito的event上,OptionsMonito在构造函数中通过DI容器获取到ConfigurationRoot._changeToken中包装类。并把event作为回调函数进行注册.

通过以上代码分析,当我们向创建一个具有相同通知机制的回调链条并且有多次通知 需要利用CancellationToken与 ChangeToken.OnChange 进行链接,同时要注意每次链接后向下发送消息时,要重新生成changeToken,因为changeToken的特性是只能发送一次消息。向多次必须重新生成ChangeToken例如

ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
previousToken.OnReload();

文章中提到的代码,请在source.dot.net快速搜索预览


本文来自博客园,作者:一身大膘,转载请注明原文链接:https://www.cnblogs.com/hts92/p/16012929.html

如果该篇文章对您有帮助的话,可以点一下右下角的【推荐】


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.依賴配置 1.1 pom文件 <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <flink.version>1.13.0< ...
  • Hibernate 是一個開放源代碼的對象關係映射框架,它對 JDBC 進行了非常輕量級的對象封裝,它將 POJO 與資料庫表建立映射關係,是一個全自動的 orm 框架 ...
  • Java程式設計基礎之面向對象(下) (補充了上的一些遺漏的知識,同時加入了自己的筆記的ヾ(•ω•`)o) (至於為什麼分P,啊大概是為了自己查筆記方便(?)應該是(〃` 3′〃)) (但是u1s1,學完了面向對象後反而更懵逼,下一步先刷演算法吧,然後Java的學習也跟上,今年爭取考完二級證書(o-ω ...
  • (Java 常見排序演算法) 彙總 序號 排序演算法 平均時間 最好情況 最差情況 穩定度 額外空間 備註 相對時間 1 冒泡演算法 O(n2) O(n) O(n2) 穩定 O(1) n 越小越好 182 ms 2 選擇演算法 O(n2) O(n2) O(n2) 不穩定 O(1) n 越小越好 53 ms ...
  • hello大家好呀,我是小樓~ 今天又帶來一次性能優化的分享,這是我剛進公司時接手的祖傳(壞笑)項目,這個項目在我的文章中屢次被提及,我在它上面做了很多的性能優化,比如《記一次提升18倍的性能優化》這篇文章,比較偏向某個細節的優化,本文更偏向巨集觀上的性能優化,可以說是個老演員了。 背景 為了新朋友能 ...
  • 一、概述 date代表一個特定的時間,精確到毫秒 二、構造方法 方法名說明 public Date() 分配一個Date對象,並初始化,以便它代表被分配的時間,精確到毫秒 public Date(long date) 分配一個Date對象,並將其初始化為表示從標準基準時間起指定的毫秒數 import ...
  • .net core IdentityServer4 認證授權 token ...
  • 1.什麼是Blazor? 有什麼優勢? ASP.NET Core Blazor 簡介 Blazor 是一個使用 Blazor 生成互動式客戶端 Web UI 的框架: 使用 C# 代替 JavaScript 來創建信息豐富的互動式 UI。 共用使用 .NET 編寫的伺服器端和客戶端應用邏輯。 將 U ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...