配置源的同步 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();

Net6Configuration & Options 源码分析 Part3 IOptionsMonitor 是如何接收到配置文件变更并同步数据源的的更多相关文章

  1. Net6 Configuration & Options 源码分析 Part2 Options

    Net6 Configuration & Options 源码分析 Part2 Options 第二部分主要记录Options 模型 OptionsConfigurationServiceCo ...

  2. Net6 Configuration & Options 源码分析 Part1

    Net6 Configuration & Options 源码分析 Part1 在Net6中配置系统一共由两个部分组成Options 模型与配置系统.它们是两个完全独立的系统. 第一部分主要记 ...

  3. Net6 DI源码分析Part3 CallSiteRuntimeResolver,CallSiteVisitor

    CallSiteRuntimeResolver CallSiteRuntimeResolver是实现了CallSiteVisitor之一. 提供的方法主要分三个部分 自有成员方法 Resolve提供服 ...

  4. JUC源码分析-集合篇(九)SynchronousQueue

    JUC源码分析-集合篇(九)SynchronousQueue SynchronousQueue 是一个同步阻塞队列,它的每个插入操作都要等待其他线程相应的移除操作,反之亦然.SynchronousQu ...

  5. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  6. ABP源码分析十八:UI Inputs

    以下图中描述的接口和类都在Abp项目的Runtime/Validation, UI/Inputs目录下的.在当前版本的ABP(0.83)中这些接口和类并没有实际使用到.阅读代码时可以忽略,无需浪费时间 ...

  7. flask源码分析

    本flask源码分析不间断更新 而且我分析的源码全是我个人觉得是很beautiful的 1 flask-login 1.1 flask.ext.login.login_required(func),下 ...

  8. Backbone源码分析(三)

    Backbone源码分析(一) Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨C ...

  9. jQuery-1.9.1源码分析系列(十五) 动画处理——外篇

    a.动画兼容Tween.propHooks Tween.propHooks提供特殊情况下设置.获取css特征值的方法,结构如下 Tween.propHooks = { _default: { get: ...

随机推荐

  1. 源码推荐 VVebo剥离的TableView绘制

    源码推荐 VVebo剥离的TableView绘制 https://github.com/johnil/VVeboTableViewDemo 此项目由VVebo剥离,希望你能通过这个demo看到我是如何 ...

  2. 重力感应 加速计- By严焕培

    //  加速计-传统用法 // //  Created by 严焕培 on 15-05-19. //  Copyright (c) 2015年 sibu. All rights reserved. / ...

  3. NoSQL 之 Redis配置与优化

    NoSQL 之 Redis配置与优化 1.关系数据库与非关系型数据库概述 2.关系数据库与非关系型数据库区别 3.非关系型数据库产生背景 4.Redis简介 5.Redis安装部署 6.Redis 命 ...

  4. Web设计

    架构:配置中心(数据发布与订阅),配置共享,服务发现,微服务鉴权,网关,负载均衡, 设计:分布式锁,延时队列 业务:日志.链路跟踪,灰度, 日志:(面向领域.业务.基础架构) 通信协议:http(1. ...

  5. Solution -「CF 494C」Helping People

    \(\mathcal{Description}\)   Link.   给定序列 \(\{a_n\}\) 和 \(m\) 个操作,第 \(i\) 个操作有 \(p_i\) 的概率将 \([l_i,r_ ...

  6. Solution -「51nod 1868」彩色树

    \(\mathcal{Description}\)   Link & 双倍经验 Link.   给定一棵 \(n\) 个结点的树,每个结点有一种颜色.记 \(g(u,v)\) 表示 \(u\) ...

  7. 在ABP VNext框架中处理和用户相关的多对多的关系

    前面介绍了一些ABP VNext架构上的内容,随着内容的细化,我们会发现ABP VNext框架中的Entity Framework处理表之间的引用关系还是比较麻烦的,一不小心就容易出错了,本篇随笔介绍 ...

  8. 对象到底是怎么new出来的

    前言:要想理解本文,必须先了解JVM的内存结构 一.创建对象的方式 new:最常见 反射:Class.newInstance() 使用clone() 反序列化 二.创建对象的步骤(对象在JVM中怎么存 ...

  9. 在k8s中使用性能分析神器:arthas

    Arthas(阿尔萨斯)是阿里巴巴开源的性能分析神器. k8s中使用arthas的三种方式 [bak]https://www.cnblogs.com/uncleyong/p/15498842.html ...

  10. Python 中线程和进程

    目录 线程和进程 一. 什么是进程 / 线程 1. 引论 2. 线程 3. 进程 4. 区别 5. 使用 二. 多线程使用 1. 常用方法 2. 常用参数 3. 多线程的应用 3.1 重写线程法 3. ...