抽丝剥茧读源码——Microsoft.Extensions.Configuration(1)
开题
既然决定了开始写博客,那就从读asp.net core的源码开始吧!对于我这个不怎么善于写文章的人来说,也算是锻炼锻炼自己归纳总结能力。
千里之行,始于足下
俗话说,“千里之行,始于足下”,我们先看看asp.net core的configuration
是如何使用的。为了方便说明,这里建立的xunit
的测试工程,使用nuget
下载microsoft.extensions.configuration
包:
var dc = new dictionary<string, string> { {"sectiona", "valuea" }, {"sectionb", "valueb" } }; var builder = new configurationbuilder().addinmemorycollection(dc); var configurationroot = builder.build(); assert.equal("valuea", configurationroot["sectiona"]); assert.equal("valueb", configurationroot["sectionb"]);
最简单的几行代码,可以看出配置文件最基本的使用流程。
构建一个
configurationbuilder
对象添加配置资源(这里使用的是
inmemorycollection
)最后使用
build()
方法生成iconfigurationroot
配置对象使用
iconfigurationroot
对象获取配置信息
这里我们从这段代码中能看出configurationbuilder
是继承了iconfigurationbuilder
这个接口,它build
出来的对象继承了iconfigurationroot
接口,iconfigurationroot
又继承了iconfiguration
接口,而这些接口都位于microsoft.extensions.configuration.abstractions
这个程序集中。
那就从这里开始抽丝剥茧,深入探究下配置部分的源码吧!请从github下载好源码。
https://github.com/aspnet/configuration =>
你是不是发现最新的src文件夹下根本没有我们想要的microsoft.extensions
等源码呀!我们需要切换到v3.1.3
或以下的版本然后下载对应版本的代码就好了。为了更方便调试代码,我建议还是将代码从github的仓库clone下来,构建源码也比较简单。
git clone
git checkout v3.1.2
restore.cmd
build.cmd
从microsoft.extensions.configuration.abstraction
开始
不说废话,上图:
从上图可以看出我们的iconfigurationbuilder
中包含一个ilist<iconfigurationsource>
对象和add(iconfigurationsource source)
方法 ,这意味着asp.net core的配置对象是支持多配置源的。 iconfigurationbuilder
通过build
方法,生成一个iconfigurationroot
对象,可通过实现父接口iconfiguration
的索引器this[string key]
获取到配置信息。对于iconfigurationprovider
对象和iconfigurationsection
我们通过对抽象的实现去探索。
进入microsoft.extensions.configuration
configurationbuilder
`build`方法最终生成了`configurationroot`对象,并初始了`list<iconfigurationprovider>`。
public iconfigurationroot build() { var providers = new list<iconfigurationprovider>(); foreach (var source in sources) { var provider = source.build(this); providers.add(provider); } return new configurationroot(providers); }
而iconfigurationprovider
是由iconfigurationsource
build生成的。
public iconfigurationbuilder add(iconfigurationsource source) { if (source == null) { throw new argumentnullexception(nameof(source)); } sources.add(source); return this; }
configurationroot
那配置信息时如何加载进去的呢?
我们先看下配置信息是如何读取的,在索引器的get
方法中,数据时是从provider
中获取到的,而且在所有的_providers
中倒序查找到provider
后就会退出查找,这也意味着我们在添加多个配置源时,最后添加的配置源会覆盖之前添加的配置源,当然这是在键值相同的情况下。
public string this[string key] { get { 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... }
那provider
的数据是从哪里来的呢?在configurationprovider
这个类中,可以看到所有的数据都来源于data
这个dictionary<string, string>
字典集合。
这个字典集合是什么时候加载的呢?
public virtual bool tryget(string key, out string value) => data.trygetvalue(key, out value);
在构造configurationroot
对象时,我们看到所有的provider
对象都调用了load()
方法。在configurationprovider
类中load()
方法是虚方法,且没有找到别的地方对data
这个变量进行赋值,那么这个时候可以猜想configurationprovider
继承类会重写这个方法,加载data
的值,那么我们去memoryconfigurationprovider
这个类中去验证一下,memoryconfigurationprovider
在构造函数中完成了data
的赋值,没有重写这个方法。汗~~~
public configurationroot(ilist<iconfigurationprovider> providers) { if (providers == null) { throw new argumentnullexception(nameof(providers)); } _providers = providers; _changetokenregistrations = new list<idisposable>(providers.count); foreach (var p in providers) { p.load(); _changetokenregistrations.add(changetoken.onchange(() =>p.getreloadtoken(), () => raisechanged())); } }
那在这里先去漫游一下,通过fileconfigurationprovider
=> jsonconfigurationprovider
找到了data = jsonconfigurationfileparser.parse(stream)
,证明我们的猜想还是没有错的。
这么一路抽丝剥茧,我们就知道了配置信息是如何运作的了!下一步我们看看配置文件是如何监视文件变化的,也对fileconfigurationprovider
这一部分细化阅读一下。