欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

抽丝剥茧读源码——Microsoft.Extensions.Configuration(1)

程序员文章站 2022-03-08 14:17:27
开题 ​ 既然决定了开始写博客,那就从读ASP.NET core的源码开始吧!对于我这个不怎么善于写文章的人来说,也算是锻炼锻炼自己归纳总结能力。 千里之行,始于足下 ​ 俗话说,“千里之行,始于足下”,我们先看看ASP.NET core的 是如何使用的。为了方便说明,这里建立的 的测试工程,使用 ......

开题

​ 既然决定了开始写博客,那就从读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开始

​ 不说废话,上图:

抽丝剥茧读源码——Microsoft.Extensions.Configuration(1)

​ 从上图可以看出我们的iconfigurationbuilder中包含一个ilist<iconfigurationsource>对象和add(iconfigurationsource source)方法 ,这意味着asp.net core的配置对象是支持多配置源的。 iconfigurationbuilder通过build方法,生成一个iconfigurationroot对象,可通过实现父接口iconfiguration的索引器this[string key]获取到配置信息。对于iconfigurationprovider对象和iconfigurationsection我们通过对抽象的实现去探索。

进入microsoft.extensions.configuration

抽丝剥茧读源码——Microsoft.Extensions.Configuration(1)
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是由iconfigurationsourcebuild生成的。

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这一部分细化阅读一下。