抽丝剥茧读源码——Microsoft.Extensions.Configuration(2)
继续抽丝剥茧
? 我们知道在使用json
、xml
或ini
等文件类配置源时,如果更改了配置文件中的内容,程序是能够感知文件变化的。这里以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 _changeTokenRegistrations;
foreach (var p in providers)
{
p.Load();
_changeTokenRegistrations.Add(ChangeToken.OnChange(() =>p.GetReloadToken(),
() => RaiseChanged()));
}
? 我们看到了_changeTokenRegistrations
这个对象调用了ChangeToken.OnChange()
静态方法,那么这个静态方法做了什么呢?
public static IDisposable OnChange(Func changeTokenProducer, Action changeTokenConsumer)
{
if (changeTokenProducer == null)
{
throw new ArgumentNullException(nameof(changeTokenProducer));
}
if (changeTokenConsumer == null)
{
throw new ArgumentNullException(nameof(changeTokenConsumer));
}
return new ChangeTokenRegistration(changeTokenProducer, callback => callback(), changeTokenConsumer);
}
? 这里引入了ChangeTokenRegistration
这个类,通过类的构造函数参数(Func
,我们可以看出
它就是将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