聊聊.Net Core配置Configuration源码
最近又研究了一下.NetCore配置选项的源码实现,又学习到了不少东西。这篇文章先写一下IConfiguration的学习成果,Options的后面补上
核心类
ConfigurationBuilder:IConfigurationBuilder (构建IConfiguration)
IConfigurationSource (配置数据来源)
IConfigurationProvider (将配置源的原始结构转为为IDictionary<string, string>)
ConfigurationRoot:IConfigurationRoot:IConfiguration (配置根节点)
构建
ConfigurationBuilder
下面是ConfigurationBuilder中的主要代码
可以看到ConfigurationBuilder的主要功能就是配置数据源到集合中
在Build时依次调用IConfigurationSource的Build函数,并将返回的IConfigurationProvider加入到List中
最后用IConfigurationProvider的集合构建一个ConfigurationRoot对象
1 public IList<IConfigurationSource> Sources = new List<IConfigurationSource>(); 2 3 public IConfigurationBuilder Add(IConfigurationSource source) 4 { 5 Sources.Add(source); 6 return this; 7 } 8 9 public IConfigurationRoot Build() 10 { 11 List<IConfigurationProvider> list = new List<IConfigurationProvider>(); 12 foreach (IConfigurationSource source in Sources) 13 { 14 IConfigurationProvider item = source.Build(this); 15 list.Add(item); 16 } 17 18 return new ConfigurationRoot(list); 19 }
IConfigurationSource
1 public class EnvironmentVariablesConfigurationSource : IConfigurationSource 2 { 3 public string Prefix; 4 public IConfigurationProvider Build(IConfigurationBuilder builder) 5 { 6 return new EnvironmentVariablesConfigurationProvider(Prefix); 7 } 8 public EnvironmentVariablesConfigurationSource() 9 { 10 } 11 } 12 13 14 public class CommandLineConfigurationSource : IConfigurationSource 15 { 16 public IDictionary<string, string> SwitchMappings; 17 public IEnumerable<string> Args; 18 public IConfigurationProvider Build(IConfigurationBuilder builder) 19 { 20 return new CommandLineConfigurationProvider(Args, SwitchMappings); 21 } 22 public CommandLineConfigurationSource() 23 { 24 } 25 } 26 27 //JsonConfigurationSource继承自FileConfigurationSource,我这里将其合为一个了 28 public abstract class JsonConfigurationSource : IConfigurationSource 29 { 30 public IFileProvider FileProvider { get; set; } 31 public string Path { get; set; } 32 public bool Optional { get; set; } 33 public bool ReloadOnChange { get; set; } 34 public int ReloadDelay { get; set; } = 250; 35 36 public Action<FileLoadExceptionContext> OnLoadException { get; set; } 37 38 public IConfigurationProvider Build(IConfigurationBuilder builder) 39 { 40 FileProvider = FileProvider ?? builder.GetFileProvider(); 41 OnLoadException = OnLoadException ?? builder.GetFileLoadExceptionHandler(); 42 return new JsonConfigurationProvider(this); 43 } 44 45 public void ResolveFileProvider() 46 { 47 if (FileProvider == null && !string.IsNullOrEmpty(Path) && System.IO.Path.IsPathRooted(Path)) 48 { 49 string directoryName = System.IO.Path.GetDirectoryName(Path); 50 string text = System.IO.Path.GetFileName(Path); 51 while (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) 52 { 53 text = System.IO.Path.Combine(System.IO.Path.GetFileName(directoryName), text); 54 directoryName = System.IO.Path.GetDirectoryName(directoryName); 55 } 56 if (Directory.Exists(directoryName)) 57 { 58 FileProvider = new PhysicalFileProvider(directoryName); 59 Path = text; 60 } 61 } 62 } 63 }
上面展示了比较常用的三种ConfigurationSource,代码都比较简单。
也很容易看出来ConfigurationSource的作用就是配置数据源,并不解析数据。
解析数据源的功能由 IConfigurationProvider完成
ConfigurationProvider
下面为IConfigurationProvider接口定义的5个函数
1 public interface IConfigurationProvider 2 { 3 bool TryGet(string key, out string value); 4 5 void Set(string key, string value); 6 7 IChangeToken GetReloadToken(); 8 9 void Load(); 10 11 IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath); 12 }
ConfigurationProvider是一个抽象类,继承了IConfigurationProvider接口
在新建Provider时一般都会选择直接继承ConfigurationProvider,接下来看一下ConfigurationProvider的几个核心方法
1 public abstract class ConfigurationProvider : IConfigurationProvider 2 { 3 private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken(); 4 5 protected IDictionary<string, string> Data= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); 6 7 public virtual bool TryGet(string key, out string value)=>Data.TryGetValue(key, out value); 8 9 public virtual void Set(string key, string value)=>Data[key] = value; 10 11 public virtual void Load(){} 12 13 public IChangeToken GetReloadToken() 14 { 15 return _reloadToken; 16 } 17 18 protected void OnReload() 19 { 20 ConfigurationReloadToken configurationReloadToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()); 21 configurationReloadToken.OnReload(); 22 }
可以推测出:
- Load函数负责从源数据读取数据然后给字典Data赋值
- ConfigurationProvider将数据存储在字典Data中,增加修改都是对字典的操作
- 每个ConfigurationProvider都会生成一个IChangeToken,在OnReload函数被调用时生成新的Token,并调用原Token的OnReload函数
ConfigurationRoot
在ConfigurationBuilder的Build函数中,我们生成了一个ConfigurationRoot,并给他传递了所有的ConfigrationProvider列表,下面我们看看他用我们的Provider都做了啥吧
1 private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); 2 3 public ConfigurationRoot(IList<IConfigurationProvider> providers) 4 { 5 _providers = providers; 6 _changeTokenRegistrations = new List<IDisposable>(providers.Count); 7 foreach (IConfigurationProvider p in providers) 8 { 9 p.Load(); 10 ChangeToken.OnChange(p.GetReloadToken, 11 delegate{ 12 var oldToken=Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); 13 oldToken.OnReload(); 14 }) 15 } 16 } 17 18 public IChangeToken GetReloadToken()=>_changeToken;
上面的代码也对部分地方进行了简化。可以看到ConfigurationRoot在生成时主要就做了两件事
- 调用Provider的Load函数,这会给Provider的Data赋值
- 读取Provider的ReloadToken,每个Provider的Reload事件都会触发ConfigurationRoot自己的ReloadToken的Reload事件
至此配置的数据源构建这块就分析完啦!
查询
常规的配置查询有两种基本方式 :索引器和GetSection(string key)
其余的GetValue等等都是一些扩展方法,本篇文章不对此进行展开研究
索引器
索引器的查询执行的方式是倒叙查询所有的Provider,然后调用Provider的TryGet函数,在查询时重名的Key,最后加入的会生效。
赋值则是依次调用每个Provider的Set函数
1 public string this[string key] 2 { 3 get 4 { 5 for (int num = _providers.Count - 1; num >= 0; num--) 6 { 7 if (_providers[num].TryGet(key, out var value)) 8 { 9 return value; 10 } 11 } 12 return null; 13 } 14 set 15 { 16 foreach (IConfigurationProvider provider in _providers) 17 { 18 provider.Set(key, value); 19 } 20 } 21 }
GetSection
1 public IConfigurationSection GetSection(string key) 2 { 3 return new ConfigurationSection(this, key); 4 } 5 6 public class ConfigurationSection : IConfigurationSection, IConfiguration 7 { 8 private readonly IConfigurationRoot _root; 9 private readonly string _path; 10 private string _key; 11 public string Value 12 { 13 get 14 { 15 return _root[Path]; 16 } 17 set 18 { 19 _root[Path] = value; 20 } 21 } 22 23 //ConfigurationPath.Combine = string.Join(":",paramList); 24 public string this[string key] 25 { 26 get 27 { 28 return _root[ConfigurationPath.Combine(Path, key)]; 29 } 30 set 31 { 32 _root[ConfigurationPath.Combine(Path, key)] = value; 33 } 34 } 35 36 public ConfigurationSection(IConfigurationRoot root, string path) 37 { 38 _root = root; 39 _path = path; 40 } 41 42 public IConfigurationSection GetSection(string key) 43 { 44 return _root.GetSection(ConfigurationPath.Combine(Path, key)); 45 } 46 47 public IEnumerable<IConfigurationSection> GetChildren() 48 { 49 return _root.GetChildrenImplementation(Path); 50 } 51 52 public IChangeToken GetReloadToken() 53 { 54 return _root.GetReloadToken(); 55 } 56 }
可以看到GetSection会生成一个ConfigurationSection对象
而ConfigurationSection在读取/设置值时实际上就是对查询的Key用:拼接,然后调用IConfigurationRoot(_root)的赋值或查询函数
关于Configuration的配置和读取的知识点大概就是以上这些了,还有更深入的涉及到对象的绑定这一块Get<> Bind<> GetChildren()等,比较难读,要一行一行代码看,以后有时间可能再研究一下
最后贴上一个从数据加载配置源并动态更新的小例子
DBConfiguration示例
1 public void Run() 2 { 3 var builder = new ConfigurationBuilder(); 4 var dataProvider = new DBDataProvider(); 5 builder.Sources.Add(new DBConfigurationSource() { DataProvider = dataProvider, ReloadOnChange = true, Table = "config" }); 6 IConfigurationRoot config = builder.Build(); 7 8 Console.WriteLine(config["time"]); 9 Task.Run(() => 10 { 11 while (true) 12 { 13 Thread.Sleep(2000); 14 dataProvider.Update("config"); 15 Console.WriteLine($"读取配置时间:{config["time"]}"); 16 } 17 }); 18 Thread.Sleep(20000); 19 }
1 public class DBConfigurationProvider : ConfigurationProvider 2 { 3 private DBConfigurationSource Source { get; } 4 public DBConfigurationProvider(DBConfigurationSource source) 5 { 6 Source = source; 7 } 8 9 public override void Load() 10 { 11 if (Source.ReloadOnChange) 12 { 13 ChangeToken.OnChange(() => Source.DataProvider.Watch(Source.Table), LoadData); 14 } 15 LoadData(); 16 } 17 18 private void LoadData() 19 { 20 var data = Source.DataProvider.GetData(Source.Table); 21 Load(data); 22 OnReload(); 23 } 24 25 public void Load(Dictionary<string, object> data) 26 { 27 var dic = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase); 28 foreach (var element in data) 29 { 30 dic.Add(element.Key, element.Value?.ToString()); 31 } 32 base.Data = dic; 33 } 34 } 35 36 public class DBConfigurationSource : IConfigurationSource 37 { 38 public DBDataProvider DataProvider { get; set; } 39 public string Table { get; set; } 40 public bool ReloadOnChange { get; set; } 41 public bool Optional { get; set; } 42 43 public DBConfigurationSource() 44 { 45 } 46 47 public IConfigurationProvider Build(IConfigurationBuilder builder) 48 { 49 return new DBConfigurationProvider(this); 50 } 51 } 52 53 public class DBDataProvider 54 { 55 private ConcurrentDictionary<string, CancellationTokenSource> tableToken = new ConcurrentDictionary<string, CancellationTokenSource>(); 56 public DBDataProvider() 57 { 58 } 59 60 public Dictionary<string, object> GetData(string table) 61 { 62 switch (table) 63 { 64 case "config": 65 return GetConfig(); 66 } 67 return new Dictionary<string, object>(); 68 } 69 70 public void Update(string table) 71 { 72 Console.WriteLine($"更新数据库数据table:{table}"); 73 if (tableToken.TryGetValue(table, out CancellationTokenSource cts)) 74 { 75 var oldCts = cts; 76 tableToken[table] = new CancellationTokenSource(); 77 oldCts.Cancel(); 78 } 79 } 80 81 private Dictionary<string, object> GetConfig() 82 { 83 var valueDic = new Dictionary<string, object>(); 84 valueDic.TryAdd("time", DateTime.Now.ToString()); 85 valueDic.TryAdd("weather", "windy"); 86 valueDic.TryAdd("people_number:male", 100); 87 valueDic.TryAdd("people_number:female", 150); 88 return valueDic; 89 } 90 91 public IChangeToken Watch(string table) 92 { 93 var cts = tableToken.GetOrAdd(table, x => new CancellationTokenSource()); 94 return new CancellationChangeToken(cts.Token); 95 } 96 }
如有侵权请联系vfphome@163.com
浙公网安备 33010602011771号