.Net Core Configuration源码探究

    上篇文章我们演示了为Configuration添加Etcd数据源,并且了解到为Configuration扩展自定义数据源还是非常简单的,核心就是把数据源的数据按照一定的规则读取到指定的字典里,这些都得益于微软设计的合理性和便捷性。本篇文章我们将一起探究Configuration源码,去了解Configuration到底是如何工作的。

ConfigurationBuilder

    相信使用了.Net Core或者看过.Net Core源码的同学都非常清楚,.Net Core使用了大量的Builder模式许多核心操作都是是用来了Builder模式,微软在.Net Core使用了许多在传统.Net框架上并未使用的设计模式,这也使得.Net Core使用更方便,代码更合理。Configuration作为.Net Core的核心功能当然也不例外。
    其实并没有Configuration这个类,这只是我们对配置模块的代名词。其核心是IConfiguration接口,IConfiguration又是由IConfigurationBuilder构建出来的,我们找到IConfigurationBuilder源码大致定义如下

public interface IConfigurationBuilder { IDictionary<string, object> Properties { get; } IList<IConfigurationSource> Sources { get; } IConfigurationBuilder Add(IConfigurationSource source); IConfigurationRoot Build(); }

Add方法我们上篇文章曾使用过,就是为ConfigurationBuilder添加ConfigurationSource数据源,添加的数据源被存放在Sources这个属性里。当我们要使用IConfiguration的时候通过Build的方法得到IConfiguration实例,IConfigurationRoot接口是继承自IConfiguration接口的,待会我们会探究这个接口。
我们找到IConfigurationBuilder的默认实现类ConfigurationBuilder大致代码实现如下

public class ConfigurationBuilder : IConfigurationBuilder { /// <summary> /// 添加的数据源被存放到了这里 /// </summary> public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>(); /// <summary> /// 添加IConfigurationSource数据源 /// </summary> /// <returns></returns> public IConfigurationBuilder Add(IConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Sources.Add(source); return this; } public IConfigurationRoot Build() { //获取所有添加的IConfigurationSource里的IConfigurationProvider var providers = new List<IConfigurationProvider>(); foreach (var source in Sources) { var provider = source.Build(this); providers.Add(provider); } //用providers去实例化ConfigurationRoot return new ConfigurationRoot(providers); } }

这个类的定义非常的简单,相信大家都能看明白。其实整个IConfigurationBuilder的工作流程都非常简单就是将IConfigurationSource添加到Sources中,然后通过Sources里的Provider去构建IConfigurationRoot。

Configuration

通过上面我们了解到通过ConfigurationBuilder构建出来的并非是直接实现IConfiguration的实现类而是另一个接口IConfigurationRoot

ConfigurationRoot

通过源代码我们可以知道IConfigurationRoot是继承自IConfiguration,具体定义关系如下

public interface IConfigurationRoot : IConfiguration { /// <summary> /// 强制刷新数据 /// </summary> /// <returns></returns> void Reload(); IEnumerable<IConfigurationProvider> Providers { get; } } public interface IConfiguration { string this[string key] { get; set; } /// <summary> /// 获取指定名称子数据节点 /// </summary> /// <returns></returns> IConfigurationSection GetSection(string key); /// <summary> /// 获取所有子数据节点 /// </summary> /// <returns></returns> IEnumerable<IConfigurationSection> GetChildren(); /// <summary> /// 获取IChangeToken用于当数据源有数据变化时,通知外部使用者 /// </summary> /// <returns></returns> IChangeToken GetReloadToken(); }

接下来我们查看IConfigurationRoot实现类ConfigurationRoot的大致实现,代码有删减

public class ConfigurationRoot : IConfigurationRoot, IDisposable { private readonly IList<IIConfigurationProvider> _providers; private readonly IList<IDisposable> _changeTokenRegistrations; private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); public ConfigurationRoot(IList<IConfigurationProvider> providers) { _providers = providers; _changeTokenRegistrations = new List<IDisposable>(providers.Count); //通过便利的方式调用ConfigurationProvider的Load方法,将数据加载到每个ConfigurationProvider的字典里 foreach (var p in providers) { p.Load(); //监听每个ConfigurationProvider的ReloadToken实现如果数据源发生变化去刷新Token通知外部发生变化 _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged())); } } //// <summary> /// 读取或设置配置相关信息 /// </summary> public string this[string key] { get { //通过这个我们可以了解到读取的顺序取决于注册Source的顺序,采用的是后来者居上的方式 //后注册的会先被读取到,如果读取到直接return for (var i = _providers.Count - 1; i >= 0; i--) { var provider = _providers[i]; if (provider.TryGet(key, out var value)) { return value; } } return null; } set { //这里的设置只是把值放到内存中去,并不会持久化到相关数据源 foreach (var provider in _providers) { provider.Set(key, value); } } } public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null); public IChangeToken GetReloadToken() => _changeToken; public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); //// <summary> /// 手动调用该方法也可以实现强制刷新的效果 /// </summary> public void Reload() { foreach (var provider in _providers) { provider.Load(); } RaiseChanged(); } //// <summary> /// 强烈推荐不熟悉Interlocked的同学研究一下Interlocked具体用法 /// </summary> private void RaiseChanged() { var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); } }

上面展示了ConfigurationRoot的核心实现其实主要就是两点

读取的方式其实是循环匹配注册进来的每个provider里的数据,是后来者居上的模式,同名key后注册进来的会先被读取到,然后直接返回

构造ConfigurationRoot的时候才把数据加载到内存中,而且为注册进来的每个provider设置监听回调

ConfigurationSection

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspzdz.html