聊聊.Net Core配置Configuration源码

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

核心类

ConfigurationBuilder:IConfigurationBuilder (构建IConfiguration)

IConfigurationSource (配置数据来源)

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

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

构建

ConfigurationBuilder

下面是ConfigurationBuilder中的主要代码

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

在Build时依次调用IConfigurationSourceBuild函数,并将返回的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

ConfigurationBuilderBuild函数中,我们生成了一个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在生成时主要就做了两件事

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

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

查询

常规的配置查询有两种基本方式 :索引器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

 

posted @ 2024-02-11 09:59  胡大C  阅读(360)  评论(0)    收藏  举报