最近又研究了一下.NetCore配置选项的源码实现,又学习到了不少东西。这篇文章先写一下IConfiguration的学习成果,Options的后面补上

核心类

ConfigurationBuilder:IConfigurationBuilder (构建IConfiguration)

IConfigurationSource (配置数据来源)

IConfigurationProvider (将配置源的原始结构转为为IDictionary<string, string>)

ConfigurationRoot:IConfigurationRoot:IConfiguration (配置根节点)

构建

ConfigurationBuilder

下面是ConfigurationBuilder中的主要代码

可以看到ConfigurationBuilder的主要功能就是配置数据源到集合中

在Build时依次调用IConfigurationSourceBuild函数,并将返回的IConfigurationProvider加入到List中

最后用IConfigurationProvider的集合构建一个ConfigurationRoot对象


public IList<IConfigurationSource> Sources = new List<IConfigurationSource>(); public IConfigurationBuilder Add(IConfigurationSource source)
{
Sources.Add(source);
return this;
} public IConfigurationRoot Build()
{
List<IConfigurationProvider> list = new List<IConfigurationProvider>();
foreach (IConfigurationSource source in Sources)
{
IConfigurationProvider item = source.Build(this);
list.Add(item);
} return new ConfigurationRoot(list);
}

IConfigurationSource

public class EnvironmentVariablesConfigurationSource : IConfigurationSource
{
public string Prefix;
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new EnvironmentVariablesConfigurationProvider(Prefix);
}
public EnvironmentVariablesConfigurationSource()
{
}
} public class CommandLineConfigurationSource : IConfigurationSource
{
public IDictionary<string, string> SwitchMappings;
public IEnumerable<string> Args;
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new CommandLineConfigurationProvider(Args, SwitchMappings);
}
public CommandLineConfigurationSource()
{
}
} //JsonConfigurationSource继承自FileConfigurationSource,我这里将其合为一个了
public abstract class JsonConfigurationSource : IConfigurationSource
{
public IFileProvider FileProvider { get; set; }
public string Path { get; set; }
public bool Optional { get; set; }
public bool ReloadOnChange { get; set; }
public int ReloadDelay { get; set; } = 250; public Action<FileLoadExceptionContext> OnLoadException { get; set; } public IConfigurationProvider Build(IConfigurationBuilder builder)
{
FileProvider = FileProvider ?? builder.GetFileProvider();
OnLoadException = OnLoadException ?? builder.GetFileLoadExceptionHandler();
return new JsonConfigurationProvider(this);
} public void ResolveFileProvider()
{
if (FileProvider == null && !string.IsNullOrEmpty(Path) && System.IO.Path.IsPathRooted(Path))
{
string directoryName = System.IO.Path.GetDirectoryName(Path);
string text = System.IO.Path.GetFileName(Path);
while (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
{
text = System.IO.Path.Combine(System.IO.Path.GetFileName(directoryName), text);
directoryName = System.IO.Path.GetDirectoryName(directoryName);
}
if (Directory.Exists(directoryName))
{
FileProvider = new PhysicalFileProvider(directoryName);
Path = text;
}
}
}
}

上面展示了比较常用的三种ConfigurationSource,代码都比较简单。

也很容易看出来ConfigurationSource的作用就是配置数据源,并不解析数据。

解析数据源的功能由 IConfigurationProvider完成

ConfigurationProvider

下面为IConfigurationProvider接口定义的5个函数

public interface IConfigurationProvider
{
bool TryGet(string key, out string value); void Set(string key, string value); IChangeToken GetReloadToken(); void Load(); IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
}

ConfigurationProvider是一个抽象类,继承了IConfigurationProvider接口

在新建Provider时一般都会选择直接继承ConfigurationProvider,接下来看一下ConfigurationProvider的几个核心方法

public abstract class ConfigurationProvider : IConfigurationProvider
{
private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken(); protected IDictionary<string, string> Data= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public virtual bool TryGet(string key, out string value)=>Data.TryGetValue(key, out value); public virtual void Set(string key, string value)=>Data[key] = value; public virtual void Load(){} public IChangeToken GetReloadToken()
{
return _reloadToken;
} protected void OnReload()
{
ConfigurationReloadToken configurationReloadToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
configurationReloadToken.OnReload();
}

可以推测出:

  • Load函数负责从源数据读取数据然后给字典Data赋值
  • ConfigurationProvider将数据存储在字典Data中,增加修改都是对字典的操作
  • 每个ConfigurationProvider都会生成一个IChangeToken,在OnReload函数被调用时生成新的Token,并调用原Token的OnReload函数

ConfigurationRoot

ConfigurationBuilderBuild函数中,我们生成了一个ConfigurationRoot,并给他传递了所有的ConfigrationProvider列表,下面我们看看他用我们的Provider都做了啥吧

private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

public ConfigurationRoot(IList<IConfigurationProvider> providers)
{
_providers = providers;
_changeTokenRegistrations = new List<IDisposable>(providers.Count);
foreach (IConfigurationProvider p in providers)
{
p.Load();
ChangeToken.OnChange(p.GetReloadToken,
delegate{
var oldToken=Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
oldToken.OnReload();
})
}
} public IChangeToken GetReloadToken()=>_changeToken;

上面的代码也对部分地方进行了简化。可以看到ConfigurationRoot在生成时主要就做了两件事

  1. 调用Provider的Load函数,这会给ProviderData赋值
  2. 读取ProviderReloadToken,每个ProviderReload事件都会触发ConfigurationRoot自己的ReloadTokenReload事件

至此配置的数据源构建这块就分析完啦!

查询

常规的配置查询有两种基本方式 :索引器GetSection(string key)

其余的GetValue等等都是一些扩展方法,本篇文章不对此进行展开研究

索引器

索引器的查询执行的方式是倒叙查询所有的Provider,然后调用Provider的TryGet函数,在查询时重名的Key,最后加入的会生效。

赋值则是依次调用每个Provider的Set函数

public string this[string key]
{
get
{
for (int num = _providers.Count - 1; num >= 0; num--)
{
if (_providers[num].TryGet(key, out var value))
{
return value;
}
}
return null;
}
set
{
foreach (IConfigurationProvider provider in _providers)
{
provider.Set(key, value);
}
}
}

GetSection

public IConfigurationSection GetSection(string key)
{
return new ConfigurationSection(this, key);
} public class ConfigurationSection : IConfigurationSection, IConfiguration
{
private readonly IConfigurationRoot _root;
private readonly string _path;
private string _key;
public string Value
{
get
{
return _root[Path];
}
set
{
_root[Path] = value;
}
} //ConfigurationPath.Combine = string.Join(":",paramList);
public string this[string key]
{
get
{
return _root[ConfigurationPath.Combine(Path, key)];
}
set
{
_root[ConfigurationPath.Combine(Path, key)] = value;
}
} public ConfigurationSection(IConfigurationRoot root, string path)
{
_root = root;
_path = path;
} public IConfigurationSection GetSection(string key)
{
return _root.GetSection(ConfigurationPath.Combine(Path, key));
} public IEnumerable<IConfigurationSection> GetChildren()
{
return _root.GetChildrenImplementation(Path);
} public IChangeToken GetReloadToken()
{
return _root.GetReloadToken();
}
}

可以看到GetSection会生成一个ConfigurationSection对象

ConfigurationSection在读取/设置值时实际上就是对查询的Key用:拼接,然后调用IConfigurationRoot(_root)的赋值或查询函数

关于Configuration的配置和读取的知识点大概就是以上这些了,还有更深入的涉及到对象的绑定这一块Get<> Bind<> GetChildren()等,比较难读,要一行一行代码看,以后有时间可能再研究一下

最后贴上一个从数据加载配置源并动态更新的小例子

DBConfiguration示例

 public void Run()
{
var builder = new ConfigurationBuilder();
var dataProvider = new DBDataProvider();
builder.Sources.Add(new DBConfigurationSource() { DataProvider = dataProvider, ReloadOnChange = true, Table = "config" });
IConfigurationRoot config = builder.Build(); Console.WriteLine(config["time"]);
Task.Run(() =>
{
while (true)
{
Thread.Sleep(2000);
dataProvider.Update("config");
Console.WriteLine($"读取配置时间:{config["time"]}");
}
});
Thread.Sleep(20000);
}
public class DBConfigurationProvider : ConfigurationProvider
{
private DBConfigurationSource Source { get; }
public DBConfigurationProvider(DBConfigurationSource source)
{
Source = source;
} public override void Load()
{
if (Source.ReloadOnChange)
{
ChangeToken.OnChange(() => Source.DataProvider.Watch(Source.Table), LoadData);
}
LoadData();
} private void LoadData()
{
var data = Source.DataProvider.GetData(Source.Table);
Load(data);
OnReload();
} public void Load(Dictionary<string, object> data)
{
var dic = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var element in data)
{
dic.Add(element.Key, element.Value?.ToString());
}
base.Data = dic;
}
} public class DBConfigurationSource : IConfigurationSource
{
public DBDataProvider DataProvider { get; set; }
public string Table { get; set; }
public bool ReloadOnChange { get; set; }
public bool Optional { get; set; } public DBConfigurationSource()
{
} public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new DBConfigurationProvider(this);
}
} public class DBDataProvider
{
private ConcurrentDictionary<string, CancellationTokenSource> tableToken = new ConcurrentDictionary<string, CancellationTokenSource>();
public DBDataProvider()
{
} public Dictionary<string, object> GetData(string table)
{
switch (table)
{
case "config":
return GetConfig();
}
return new Dictionary<string, object>();
} public void Update(string table)
{
Console.WriteLine($"更新数据库数据table:{table}");
if (tableToken.TryGetValue(table, out CancellationTokenSource cts))
{
var oldCts = cts;
tableToken[table] = new CancellationTokenSource();
oldCts.Cancel();
}
} private Dictionary<string, object> GetConfig()
{
var valueDic = new Dictionary<string, object>();
valueDic.TryAdd("time", DateTime.Now.ToString());
valueDic.TryAdd("weather", "windy");
valueDic.TryAdd("people_number:male", 100);
valueDic.TryAdd("people_number:female", 150);
return valueDic;
} public IChangeToken Watch(string table)
{
var cts = tableToken.GetOrAdd(table, x => new CancellationTokenSource());
return new CancellationChangeToken(cts.Token);
}
}

.Net Core配置Configuration源码研究的更多相关文章

  1. .Net Core Configuration源码探究

    前言     上篇文章我们演示了为Configuration添加Etcd数据源,并且了解到为Configuration扩展自定义数据源还是非常简单的,核心就是把数据源的数据按照一定的规则读取到指定的字 ...

  2. 从源码研究如何不重启Springboot项目实现redis配置动态切换

    上一篇Websocket的续篇暂时还没有动手写,这篇算是插播吧.今天讲讲不重启项目动态切换redis服务. 背景 多个项目或微服务场景下,各个项目都需要配置redis数据源.但是,每当运维搞事时(修改 ...

  3. springboot脚手架liugh-parent源码研究参考

    1. liugh-parent源码研究参考 1.1. 前言 这也是个开源的springboot脚手架项目,这里研究记录一些该框架写的比较好的代码段和功能 脚手架地址 1.2. 功能 1.2.1. 当前 ...

  4. Android开源项目 Universal imageloader 源码研究之Lru算法

    https://github.com/nostra13/Android-Universal-Image-Loader universal imageloader 源码研究之Lru算法 LRU - Le ...

  5. 阿里sentinel源码研究深入

    1. 阿里sentinel源码研究深入 1.1. 前言 昨天已经把sentinel成功部署到线上环境,可参考我上篇博文,该走的坑也都走了一遍,已经可以初步使用它的限流和降级功能,根据我目前的实践,限流 ...

  6. Chrome自带恐龙小游戏的源码研究(七)

    在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较 ...

  7. Chrome自带恐龙小游戏的源码研究(五)

    在上一篇<Chrome自带恐龙小游戏的源码研究(四)>中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现. 会眨眼睛的恐龙 在游戏开始前的待机界面,如果仔细观 ...

  8. Chrome自带恐龙小游戏的源码研究(四)

    在上一篇<Chrome自带恐龙小游戏的源码研究(三)>中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物. 障碍物有两种:仙人掌和翼龙.仙人掌有大小两种类型,可以同时并列多个:翼龙按高. ...

  9. Springboot中注解@Configuration源码分析

    Springboot中注解@Configuration和@Component的区别 1.先说结论,@Configuration注解上面有@Component注解,所以@Component有的功能@Co ...

随机推荐

  1. Bootstrap 树形列表与右键菜单

    Bootstrap 树形列表与右键菜单 介绍两个Bootstrap的扩展 Bootstrap Tree View 树形列表 jQuery contextMenu 右键菜单 Demo采用CDN分发,直接 ...

  2. SpringBoot自动装配-源码分析

    1. 简介 通过源码探究SpringBoot的自动装配功能. 2. 核心代码 2.1 启动类 我们都知道SpringBoot项目创建好后,会自动生成一个当前模块的启动类.如下: import org. ...

  3. anyRTC 6月SDK更新迭代

    anyRTC 6月更新迭代,macOS新增屏幕 ID 进行屏幕共享功能,让共享更高效简单:此外解决了视频的宽高不是16:9导致共享内容缺失的问题,同时对音视频模块,推流组件等多项功能进行了优化改进. ...

  4. 通过比较rgb破解滑动验证码

    目标url:信用中国(广东中山)行政处罚 /** * base64转BufferedImage * * @param base64 * @return */ public static Buffere ...

  5. Solidity

    起因是Xenc师傅给我截了张图,我日 居然看不懂 ,一搜才知道,之前学的版本有些老了.. 这次学下新一点的记录下 HelloWorld pragma solidity ^0.6.0; // versi ...

  6. 三年Android开发,竟只会增删改查,被面试官一顿怼!

    最近看到某公司面试官发的这样一个帖子: 我面试了一个有三年Android开发经验的小伙子,也是我有史以来给别人面试时间最短的一次,不到十分钟就结束了,原因很简单,底子太差只会curd,很多技术性的问题 ...

  7. 程序员被老板要求两个月做个APP,要不比京东差,网友:做一个快捷方式,直接链到京东

    隔行如隔山,这句话说得一点都没错.做一个程序员,很多人都会羡慕,也有很多人会望而却步. 作为一个外行人,你别看程序员每天坐在电脑前敲敲键盘打打代码,以为很简单,其实啊也只有程序员自己明白,任何一个看似 ...

  8. .NET 6 预览版 7:新功能已完成 ,将专注于改进

    .NET 团队的项目经理 Richard Lander在宣布 .NET 6 Preview 7 时说:"这是 .NET 预览的又一季的结束.", 中文翻译:.NET 6 预览版 7 ...

  9. JAVA基础语法:常用功能符以及循环结构和分支结构(转载)

    3.JAVA基础语法:常用功能符以及循环结构和分支结构 1.常用功能符 注释 ("文字"是被注释的部分) //文字 单行注释 /文字/ 多行注释 算术运算符 + - * / / 整 ...

  10. CYPEESS USB3.0程序解读之---同步FIFO(slaveFifoSync)

    上一篇文章解读了CYPRESS FX3的GPIO的操作过程,下面解读同步FIFO的一个例子(slaveFifoSync). *生产者,消费者. 1.首先看DMA的回调函数(cyu3dma.h): ty ...