zoukankan      html  css  js  c++  java
  • 抽丝剥茧读源码——Microsoft.Extensions.Configuration(2)

    继续抽丝剥茧

    ​ 我们知道在使用jsonxmlini等文件类配置源时,如果更改了配置文件中的内容,程序是能够感知文件变化的。这里以json配置源为例,查看AddJsonFile这个方法的定义,我们看到在添加json配置源的时候,有这么两个参数:

    AddJsonFile(this IConfigurationBuilder builder, 
                string path, bool optional, bool reloadOnChange);
    

    optional:配置文件可选

    reloadOnChange:配置文件修改时进行重新加载

    ​ OK,动手操练操练。

    test.json文件:

    {
      "SectionA": "ValueA",
      "SectionB": "ValueB"
    }
    

    测试代码:

    var builder = new ConfigurationBuilder().AddJsonFile("test.json",false,true);
    IConfigurationRoot configurationRoot = builder.Build();
    Assert.Equal("ValueA", configurationRoot["SectionA"]);
    Assert.Equal("ValueB", configurationRoot["SectionB"]);
    Thread.Sleep(3000);  //这时将文件中SectionA的值更改为ValueA+
    Assert.Equal("ValueA+", configurationRoot["SectionA"]);
    

    ​ 完美通过测试。那么,配置信息是如何监视文件变化的呢?

    CancellationTokenSource的使用

    ​ 为了弄清楚配置信息是如何监视文件变化之前,我们先看下CancellationTokenSource的简单应用。

    var source = new CancellationTokenSource();
    source.Token.Register(() => Console.WriteLine("This is a callback"));
    if(!source.IsCancellationRequested)
    {
    	source.Cancel();
    }
    // 控制台输出 "This is a callback"
    

    CancellationTokenSource多用在取消线程操作中,这里使用了CancellationToken注册回调的特性,使用Cancel()方法时触发回调函数。配置文件的重加载就是通过这个原理实现的,所以在接下来IChangeToken接口的实现类中,我们就能发现CancellationTokenSource的身影。

    引入FileExtension

    ​ 目前我们引入了文件配置源,并使用json文件作为了测试文件,那我们再把之前的图再补充一下。

    ConfigurationRoot

    ​ 追溯一下我们在加载配置源时的代码。

    //private readonly IList<IDisposable> _changeTokenRegistrations;
    foreach (var p in providers)
    {
    	p.Load();
    	_changeTokenRegistrations.Add(ChangeToken.OnChange(() =>p.GetReloadToken(),
    	() => RaiseChanged()));
    }
    

    ​ 我们看到了_changeTokenRegistrations这个对象调用了ChangeToken.OnChange()静态方法,那么这个静态方法做了什么呢?

    public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
    {
    	if (changeTokenProducer == null)
    	{
    		throw new ArgumentNullException(nameof(changeTokenProducer));
    	}
    	if (changeTokenConsumer == null)
    	{
    		throw new ArgumentNullException(nameof(changeTokenConsumer));
    	}
    	return new ChangeTokenRegistration<Action>(changeTokenProducer, callback => callback(), changeTokenConsumer);
    }
    

    ​ 这里引入了ChangeTokenRegistration这个类,通过类的构造函数参数(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state),我们可以看出

    它就是将token的生产者changeTokenProducer和消费者changeTokenConsumer做一个绑定操作,绑定的消费函数就是ConfigurationRoot中的RaiseChanged()函数。

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

    ​ 通过这个函数就看到previousToken执行了OnReload()函数,这里我们再跟踪到此函数的定义时,我们就发现了这段代码。

    public void OnReload() => _cts.Cancel();
    

    ​ 是不是有点清晰了,但是通过CancellationTokenSource的使用我们知道,token是需要绑定callback函数的,那么这个注册是在哪里进行的呢?我们再回到ChangeTokenRegistration这个类中。

    private void OnChangeTokenFired()
    {
    	// The order here is important. We need to take the token and then apply our changes BEFORE
    	// registering. This prevents us from possible having two change updates to process concurrently.
    	// If the token changes after we take the token, then we'll process the update immediately upon
    	// registering the callback.
    	var token = _changeTokenProducer();
    	try
    	{
    		_changeTokenConsumer(_state);
    	}
    	finally
    	{
    		// We always want to ensure the callback is registered
    		RegisterChangeTokenCallback(token);
    	}
    }
    

    ​ 在ChangeTokenRegistration的构造函数中,我们知道token注册了OnChangeTokenFired()这个函数回调,这个函数主要做了三件事情:

    token生产者生产一个新的token

    消费token,触发消费函数

    将新的token重新注册一个到此回调函数

    ​ 这样在token失效后又被重新注册了。

    ​ 但是,但是,但是,重要的事情说三遍。这里还不是监视文件变化部分的原理哦,这里只是多配置源用来监视配置文件重载的,那么监视文件变化其实也是这个原理,这里理顺了,文件变化只要找到使用ChangeToken.OnChange()这个静态方法的地方就可以了,文件变化也就理解了。

    FileConfigurationProvider

    ​ 在FileConfigurationProvider类的构造函数中,我们就找到了监视文件变化的源头了。这里的消费者函数,就是Load()函数,一旦监视到文件变化就调用Load对文件进行重新加载。

    if (Source.ReloadOnChange && Source.FileProvider != null)
    {
    	_changeTokenRegistration = ChangeToken.OnChange(
    	() => Source.FileProvider.Watch(Source.Path),
    	() => {
    			Thread.Sleep(Source.ReloadDelay);
    			Load(reload: true);
    	});
    }
    

    ​ 这里的Watch函数用到的原理是FileSystemWatcher这个类,这里不过多阐述,大家可自行查看,它主要有以下几个事件.

    public event FileSystemEventHandler Deleted

    public event FileSystemEventHandler Created

    public event FileSystemEventHandler Changed

    public event RenamedEventHandler Renamed

    public event ErrorEventHandler Error;

    ​ 配置文件方面的原理也就逐渐清晰了,不过原理易懂,设计思想难懂,希望大家多借鉴其中的设计理念,用好别人的代码,也写好自己的代码。
    个人博客:www.corecoder.cn

  • 相关阅读:
    使用 cordova-plugin-wechat 分享返回后闪退解决方法
    恢复删除的表
    移动端还原设计图
    js文本差异比较
    windows使用nvm安装nodejs后升级npm报错
    windows添加右键菜单"在此处打开cmd窗口"
    cordova热更新
    js变量提升
    c# 判断字符串是否是日期格式需要注意的一点小问题
    剑指offer_和为S的两个数字
  • 原文地址:https://www.cnblogs.com/boydenyol/p/12941642.html
Copyright © 2011-2022 走看看