在《配置模型总体设计》介绍配置模型核心对象的时候,我们刻意回避了与配置同步相关的API,现在我们利用一个独立文章来专门讨论这个话题。配置的同步涉及到两个方面:第一,对原始的配置源实施监控并在其发生变化之后重新加载配置;第二,配置重新加载之后及时通知应用程序进而使应用能够及时使用最新的配置。要了解配置同步机制的实现原理,我们先得了解一下配置数据的流向。

一、配置数据流

通过前面的介绍,我们已经对配置模型有了充分的了解,处于核心地位的 IConfigurationBuilder对象借助注册的IConfigurationSource对象提供的IConfigurationProvider对象从相应的配置源中加载数据,而各种针对IConfigurationProvider接口的实现就是为了将形态各异的原始配置数据转换成配置字典。我们在应用程序中使用的配置数据直接来源于IConfigurationBuilder对象创建的IConfiguration对象,那么当我们调用定义在IConfiguration对象上的API获取配置数据时,配置数据究竟具有怎样的流向呢?

我们在前面已经提到过,由ConfigurationBuilder(IConfigurationBuilder接口的默认实现)的Build方法提供的IConfiguration对象是一个ConfigurationRoot对象,它代表着整颗配置树,而组成这棵树的配置节则通过ConfigurationSection对象表示。这棵由ConfigurationRoo对象表示的配置树其实是无状态的,也就说不论是ConfigurationRoot对象还是ConfigurationSection对象,它们并没有利用某个字段存储任何的配置数据。

ConfigurationRoot对象保持着对所有注册IConfigurationSource提供的IConfigurationProvider对象的引用,当我们调用ConfigurationRoot或者ConfigurationSection相应的API提取配置数据时,最终都会直接从这些IConfigurationProvider中提取数据。换句话说,配置数据在整个模型中只以配置字典的形式存储在IConfigurationProvider对象上面。

应用程序在读取配置时产生的数据流基本体现在上图中。接下来我们从ConfigurationRoot和ConfigurationSection这两个类型的定义来对这个数据流,以及建立在此基础上的配置同步机制作进一步的介绍,不过在这之前我们得先来了解一个名为ConfigurationReloadToken的类型。

二、ConfigurationReloadToken

ConfigurationRoot和ConfigurationSection的GetReloadToken方法返回的IChangeToken对象类型都是ConfigurationReloadToken。不仅如此,对于组成同一棵配置树的所有节点对应的IConfiguration对象(ConfigurationRoot或者ConfigurationSection)来说,它们的GetReloadToken方法返回的其实是同一个ConfigurationReloadToken对象。

还有一点值得强调,IConfiguration接口的GetReloadToken方法返回的IChangeToken,其作用不是在配置源发生变化时向应用程序发送通知,它实际上是通知应用程序:配置源已经发生改变,并且新的数据已经被相应的IConfigurationProvider重新加载进来。由于ConfigurationRoot和ConfigurationSection对象都不维护任何数据,它们仅仅将我们的API调用转移到IConfigurationProvider对象上,所以应用程序使用原来的IConfiguration对象就可以获取到最新的配置数据。

ConfigurationReloadToken本质上是对一个CancellationTokenSource对象的封装。从如下的代码片段可以看出,ConfigurationReloadToken与CancellationChangeToken具有类似的定义和实现。两者唯一不同之处在于:CancellationChangeToken对象利用创建时提供的CancellationTokenSource对象对外发送通知,而ConfigurationReloadToken对象则通过调用OnReload方法利用内置的CancellationTokenSource对象发送通知。

public class ConfigurationReloadToken : IChangeToken
{
private CancellationTokenSource _cts = new CancellationTokenSource();
public IDisposable RegisterChangeCallback(Action<object> callback, object state) =>_cts.Token.Register(callback, state);
public bool ActiveChangeCallbacks => True;
public bool HasChanged =>_cts.IsCancellationRequested; public void OnReload() => _cts.Cancel();
}

三、ConfigurationRoot对象

接下来我们来看看由ConfigurationBuilder对象的Build方法直接创建的ConfigurationRoot对象具有怎样的实现。正如我们前面所说,一个ConfigurationRoot对象根据一组IConfigurationProvider对象创建,这些IConfigurationProvider对象则由注册的IConfigurationSource对象来提供。

public class ConfigurationRoot : IConfigurationRoot
{
private IList<IConfigurationProvider> _providers;
private ConfigurationReloadToken _changeToken; public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
_providers = providers;
_changeToken = new ConfigurationReloadToken();
foreach (var provider in providers)
{
provider.Load();
ChangeToken.OnChange( () => provider.GetReloadToken(), () => RaiseChanged());
}
}
public void Reload()
{
foreach (var provider in _providers)
{
provider.Load();
}
RaiseChanged();
}
public IChangeToken GetReloadToken() => _changeToken;
private void RaiseChanged() => Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()) .OnReload();
...
}

ConfigurationRoot的GetReloadToken方法返回的是一个ConfigurationReloadToken对象,该对象通过字段_changeToken表示。我们知道如果需要利用这个对象对外发送配置重新加载的通知,需要调用其OnReload方法就可以了,通过上面的代码片段我们知道该方法会在RaiseChanged方法中被调用。由于一个IChangeToken对象只能发送一次通知,所以该方法还负责创建新的ConfigurationReloadToken对象并对_changeToken字段赋值。

换句话说,一旦ConfigurationRoot的RaiseChanged方法被调用,我们就可以利用其GetReloadToken方法返回的IChangeToken对象接收到配置被重新加载的通知。通过上面提供的代码,我们可以看到这个RaiseChanged方法在两个地方被调用:第一,在构造函数中调用每个IConfigurationProvider对象的GetReloadToken方法得到对应的IChangeToken对象后,并为它们注册的回调中调用了这个方法;第二,实现的Reload方法依次调用每个IConfigurationProvider对象的Load方法重新加载配置数据之后,调用了这个RaiseChanged方法。按照这个逻辑,应用程序会在如下两个场景中利用ConfigurationRoot返回的IChangeToken接收到配置被重新加载的通知:

  • 某个IConfigurationProvider对象捕捉到对应配置源的改变后自动重新加载配置,并在加载完成后利用其GetReloadToken方法返回的IChangeToken发送通知;
  • 我们显式调用ConfigurationRoot的Reload方法手动加载配置。

在了解了ConfigurationRoot的GetRealodToken返回的是什么样的IChangeToken之后,我们接着介绍它的其他成员具有怎样的实现 。如下面的代码片段所示,在ConfigurationRoot的索引定义中,它分别调用了IConfigurationProvider对象的TryGet和Set方法根据配置字典的Key获取和设置对应的Value。

public class ConfigurationRoot : IConfigurationRoot
{
private IList<IConfigurationProvider> _providers; public string this[string key]
{
get
{
foreach (var provider in _providers.Reverse())
{
if (provider.TryGet(key, out var value))
{
return value;
}
}
return null;
}
set
{
foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
} public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); public IEnumerable<IConfigurationSection> GetChildren() => GetChildrenImplementation(null); internal IEnumerable<IConfigurationSection> GetChildrenImplementation( string path)
{
return _providers
.Aggregate(Enumerable.Empty<string>(), (seed, source) => source.GetChildKeys(seed, path))
.Distinct()
.Select(key => GetSection(path == null ? key : $"{path}:{key}"));
} public IEnumerable<IConfigurationProvider> Providers => _providers;
}

从索引的定义可以看出,ConfigurationRoot在读取Value值时针对IConfigurationProvider列表的遍历是从后往前的,这一点非常重要,因为该特性决定了IConfigurationSource的注册会采用“后来居上”的原则。也就说如果多个IConfigurationSource配置源提供的IConfiguationProvider对象包含同名的配置项,后面注册的IConfigurationSource对象具有更高选择优先级,我们应该根据这个特性合理安排IConfigurationSource对象的注册顺序。在进行Value的设置的时候,ConfigurationRoot对象会调用每个IConfigurationProvider对象的Set方法,这意味着新的值会被保存到所有IConfigurationProvider对象的配置字典中。

正如我们前面多次提到过的,通过ConfigurationRoot表示的配置树的所有配置节都是一个类型为ConfigurationSection的对象,这一点体现在实现的GetSection方法上。将对应的路径作为参数,我们可以得到组成配置树的所有配置节。用于获取所有子配置节的GetChildren方法通过调用内部方法GetChildrenImplementation来实现。GetChildrenImplementation方法旨在获取配置树某个节点的所有子节点,该方法的参数表示指定节点针对配置树根的路径。当这个方法被执行的时候,它会以聚合的形式遍历所有的IConfigurationProvider并调用它们的GetChildKeys方法获取所有子节点的Key,这些Key与当前节点的路径进行合并后代表子节点的路径,这些路径最终被作为参数调用GetSection方法创建出对应的配置节。

四、ConfigurationSection对象

如下所示的代码片段大体上体现了代表配置节的ConfigurationSection类型的实现逻辑。如下面的代码片段所示,一个ConfigurationSection对象通过代表配置树根的ConfigurationRoot对象和当前配置节在配置树中的路径来构建。ConfigurationSection的Path属性直接返回构建时指定的路径,而Key属性则由根据这个路径解析出来 。

public class ConfigurationSection : IConfigurationSection
{
private readonly ConfigurationRoot _root;
private readonly string _path;
private string _key; public ConfigurationSection(ConfigurationRoot root, string path)
{
_root = root;
_path = path;
} public string this[string key]
{
get => _root[string.Join(':', new string[] { _path, _key })];
set => _root[string.Join(':', new string[] { _path, _key })] = value;
} public string Key => _key ?? (_key = _path.Contains(':') ? _path.Split(':').Last() : _path);
public string Path => _path;
public string Value
{
get => _root[_path];
set => _root[_path] = value;
}
public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(_path);
public IChangeToken GetReloadToken() => _root.GetReloadToken();
public IConfigurationSection GetSection(string key) => _root.GetSection(string.Join(':', new string[] { _path, key }));
}

如下图6-15所示,实现在ConfigurationSection类型中的大部分成员都是调用ConfigurationRoot对象相应的API来实现的。ConfigurationSection的索引直接调用ConfigurationRoot的索引来获取或者设置配置字典的Value,GetChildren方法返回的就是调用GetChildrenImplementation方法得到的结果,而GetReloadToken和GetSection方法都是通过调用同名方法实现的。

[ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]
[ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]
[ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计
[ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象
[ASP.NET Core 3框架揭秘] 配置[5]:配置数据与数据源的实时同步
[ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]
[ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]
[ASP.NET Core 3框架揭秘] 配置[8]:多样化的配置源[下篇]
[ASP.NET Core 3框架揭秘] 配置[9]:自定义配置源

[ASP.NET Core 3框架揭秘] 配置[5]:配置数据与数据源的实时同步的更多相关文章

  1. [ASP.NET Core 3框架揭秘] Options[1]: 配置选项的正确使用方式[上篇]

    依赖注入不仅是支撑整个ASP.NET Core框架的基石,也是开发ASP.NET Core应用采用的基本编程模式,所以依赖注入十分重要.依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式 ...

  2. [ASP.NET Core 3框架揭秘] Options[2]: 配置选项的正确使用方式[下篇]

    四.直接初始化Options对象 前面演示的几个实例具有一个共同的特征,即都采用配置系统来提供绑定Options对象的原始数据,实际上,Options框架具有一个完全独立的模型,可以称为Options ...

  3. [ASP.NET Core 3框架揭秘] 配置[1]:读取配置数据[上篇]

    提到"配置"二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化 ...

  4. [ASP.NET Core 3框架揭秘] 配置[2]:读取配置数据[下篇]

    [接上篇]提到“配置”二字,我想绝大部分.NET开发人员脑海中会立即浮现出两个特殊文件的身影,那就是我们再熟悉不过的app.config和web.config,多年以来我们已经习惯了将结构化的配置定义 ...

  5. [ASP.NET Core 3框架揭秘] 配置[3]:配置模型总体设计

    在<读取配置数据>([上篇],[下篇])上面一节中,我们通过实例的方式演示了几种典型的配置读取方式,接下来我们从设计的维度来重写认识配置模型.配置的编程模型涉及到三个核心对象,分别通过三个 ...

  6. [ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]

    物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

  7. [ASP.NET Core 3框架揭秘] 配置[6]:多样化的配置源[上篇]

    .NET Core采用的这个全新的配置模型的一个主要的特点就是对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源.如果采用物理文件作为配置源,我们可以选择 ...

  8. [ASP.NET Core 3框架揭秘] 配置[4]:将配置绑定为对象

    虽然应用程序可以直接利用通过IConfigurationBuilder对象创建的IConfiguration对象来提取配置数据,但是我们更倾向于将其转换成一个POCO对象,以面向对象的方式来使用配置, ...

  9. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

随机推荐

  1. ViewPage+Fragment的使用用法

    一.概述 从前面几篇文章,我们知道,实现ViewPager是要有适配器的,我们前面用的适配器是PagerAdapter,而对于fragment,它所使用的适配器是:FragmentPagerAdapt ...

  2. 物联网架构成长之路(47)-利用GitLab实现CI持续集成

    0.前言 前段时间,考虑到要练习部署一套CI/CD的系统.一开始考虑到Jenkins,随着这两天的了解,发现最新版的GitLab已经提供有CI/CD集成了.所以本次博客,干脆一步到位,直接用GitLa ...

  3. 图解 Spring:HTTP 请求的处理流程与机制【4】

    4. HTTP 请求在 Spring 框架中的处理流程 在穿越了 Web 容器和 Web 应用之后,HTTP 请求将被投送到 Spring 框架,我们继续剖析后续流程.Web 应用与 Spring M ...

  4. 洛谷P1402——乒乓球

    原题链接: https://www.luogu.com.cn/problem/P1042 题面简述 国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及.其中11分制 ...

  5. 音视频入门-14-JPEG文件格式详解

    * 音视频入门文章目录 * JPEG 文件格式解析 JPEG 文件使用的数据存储方式有多种.最常用的格式称为 JPEG 文件交换格式(JPEG File Interchange Format,JFIF ...

  6. 【springcloud】3.记一次网关优化

    今天早上过来突然被告知我们提供给外系统的接口服务出问题了,失败率高达20% 很奇怪,昨天周末,今天也没做什么处理,怎么突然变成这样了 1.接口测试 第一反应是接口是不是出问题了,然后我立马打开jmet ...

  7. 关于HashMap容量的初始化,还有这么多学问。

    在<HashMap中傻傻分不清楚的那些概念>文章中,我们介绍了HashMap中和容量相关的几个概念,简单介绍了一下HashMap的扩容机制. 文中我们提到,默认情况下HashMap的容量是 ...

  8. JS进阶面试题整理(仅仅整理我做错的题)

    前几天看到掘金博客一篇文章,找到了这个JavaScript进阶问题列表:现在把地址贴出来,想找工作或者想要巩固自己JS的同学可以参考 该文档会不定时更新    一.箭头函数 箭头函数相当于匿名函数,并 ...

  9. sqlserver查询(子查询,全连接,等值连接,自然连接,左右连,交集,并集,差集)

    --部门表 create table dept( deptno int primary key,--部门编号 dname ),--部门名 loc )--地址 ); --雇员表 create table ...

  10. 不要再造轮子了:聊一聊 JavaScript 的 URL 对象是什么?

    本文由葡萄城技术团队于博客园翻译并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 如果我们自己编写从URL中分析和提取元素的代码,那么有可能会比较痛苦 ...