.Net Core 配置源码学习 (一)
一 背景
相比.Net Framework , .NET Core的配置系统 ,有一些明显的优点 ,如:
1 支持更丰富的配置源
2 读取配置时, 可以用相同的方式读取, 并且非常方便
3 修改配置后,不用重启应用
本期对配置相关的源码简单梳理一下。 只说主逻辑 ,并且从用的角度说起 ,逐步深入。
二 配置读取
2.1 举个栗子
进入上代码环节 ,新建控制台应用 , 只需要引入 "Microsoft.Extensions.Configuration" 1个nuget包 ,就可以使用基本的配置了 。
(不得不说,相较于.NET Framework,.Net Core 的组件化设计实现的相当彻底 )
//数据源
var dicData = new Dictionary<string, string>() { { "Key1", "Value1" }, { "Key2", "Value2" } }; IConfigurationRoot configurationRoot = new ConfigurationBuilder()
.Add(new MemoryConfigurationSource() { InitialData = dicData }) // 添加配置源
.Build(); // 构建配置
Console.WriteLine("Key1=" + configurationRoot["Key1"]);//输出内容 Key1=Value1
我们好奇 configurationRoot["Key1"] 是怎么读取到 "Value1" 呢 ? 这就需要看源码了。 IConfigurationRoot到底是什么 , 以及 ["key1"] 的代码执行逻辑是怎样的 。
(注意: 为了方便了解主线逻辑 , 本文的中 .Net Core 源码有删减 )
2.2 IConfigurationRoot 相关源码
public interface IConfiguration
{
string this[string key] { get; set; }
} public interface IConfigurationRoot : IConfiguration
{
IEnumerable<IConfigurationProvider> Providers { get; }
} public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
private readonly IList<IConfigurationProvider> _providers; public IEnumerable<IConfigurationProvider> Providers => _providers; /// <summary>
/// 根据指定的Key获取或者设置对应的Value
/// </summary>
/// <param name="key">The configuration key.</param>
/// <returns>The configuration value.</returns>
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
{
if (!_providers.Any())
{
throw new InvalidOperationException(Resources.Error_NoSources);
} foreach (var provider in _providers)
{
provider.Set(key, value);
}
}
}
1 通过上面的代码发现 ,IConfigurationRoot 有个 Providers 属性 ;
2 读取配置是通过ConfigurationRoot类中的索引器实现的 , 更具体的说就是通过 遍历Providers ,调用其.TryGet(key, out var value) 获取的 。
3 细心的同学会发现,一旦获取到配置就结束并返回了。那么就有了从哪先获取的问题 ,我们发现遍历时是LIFO(后入先出)的顺序,所以后面添加的Provider有更高的优先级 。
到目前为止 ,我们只需要记住一个主知识点 :获取配置是由多个 Provider(供应者) provide (供应) 的 ,并且越后面的 Provider 优先级越高,具有覆盖性。
再想深入就得研究一下 : IConfigurationProvider是怎么回事 , 特别是其中的 TryGet 方法是怎么个逻辑 。
2.3 IConfigurationProvider 相关源码
public interface IConfigurationProvider
{
bool TryGet(string key, out string value);
void Load();
} public abstract class ConfigurationProvider : IConfigurationProvider
{
/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; } /// <summary>
/// 根据指定的 键 获取对应的 值, 获取到返回true,否则返回 false
/// </summary>
public virtual bool TryGet(string key, out string value)
=> Data.TryGetValue(key, out value); /// <summary>
/// 加载数据
/// </summary>
public virtual void Load()
{
}
}
代码非常简单 ,每个Provider 维护了一个 Data (字典类型) 属性 , 配置就是从这个字典拿到的。 到此基本的读取配置逻辑我们已经知道了 ,给自己点个赞吧
2.4 使用便利性
这时 ,我们可以再看开头那个栗子,读取配置其实还有这种写法 :
configurationRoot.Providers.First().TryGet("Key1", out string Val);
Console.WriteLine("Key1=" + Val); //输出 Val=Value1
但是这种写法就感觉有些累赘了,还是 configurationRoot["Key1"] 更方便一些 , 有时我们甚至连"[" 和 "]" 都不想打 !
从使用的角度考虑,框架做了一些封装,并提供了不少扩展方法。但是基本的读取逻辑都是一样的。
如果我们引入 "Microsoft.Extensions.Configuration.Binder" nuget包 ,读取配置操作时会发现一些新的方法。
比如配置和强模型绑定,并结合泛型使用 ,如:
public static T Get<T>(this IConfiguration configuration) public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)
三 配置写入
现在我们需要找Provider中的Data 属性是怎么写入的 ,还从开头的控制台程序入手 , 其中有这样一段代码 :
new ConfigurationBuilder().Add(new MemoryConfigurationSource() { InitialData = dicData }).Build();
我们重点看Add方法
3.1 IConfigurationBuilder 相关源码
public interface IConfigurationBuilder
{
IList<IConfigurationSource> Sources {get;} IConfigurationBuilder Add(IConfigurationSource source); IConfigurationRoot Build();
} /// <summary>
/// Used to build key/value based configuration settings for use in an application.
/// </summary>
public class ConfigurationBuilder : IConfigurationBuilder
{
/// <summary>
/// Returns the sources used to obtain configuration values.
/// </summary>
public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); /// <summary>
/// Adds a new configuration source.
/// </summary>
/// <param name="source">The configuration source to add.</param>
/// <returns>The same <see cref="IConfigurationBuilder"/>.</returns>
public IConfigurationBuilder Add(IConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} Sources.Add(source);
return this;
} /// <summary>
/// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in
/// <see cref="Sources"/>.
/// </summary>
/// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns>
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);
}
}
Add就是把配置源 Source ,添加到内部的Sources列表。IConfigurationBuilder 除了Add方法还有一个 Build 方法。
IConfigurationBuilder 就像一个车间 ,Add一堆零件后 ,Build一下, 整出来一辆 汽车 (IConfigurationRoot) , 很明显IConfigurationBuilder是一个构建者模式 。
我们继续探索 IConfigurationSource ,看 Build 方法的实现逻辑 .
3.2 IConfigurationSource 相关源码
public interface IConfigurationSource
{
IConfigurationProvider Build(IConfigurationBuilder builder);
} public class MemoryConfigurationSource : IConfigurationSource
{
/// <summary>
/// The initial key value configuration pairs.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> InitialData { get; set; } /// <summary>
/// Builds the <see cref="MemoryConfigurationProvider"/> for this source.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param>
/// <returns>A <see cref="MemoryConfigurationProvider"/></returns>
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new MemoryConfigurationProvider(this);
}
}
可以看到 ,IConfigurationSource 只干了一件事 :其 Build 方法返回一个Provider。
还拿开头的栗子来讲 ,MemoryConfigurationSource.Build 方法返回了 一个新的 MemoryConfigurationProvider 实例。
再看下 MemoryConfigurationProvider
public class MemoryConfigurationProvider : ConfigurationProvider, IEnumerable<KeyValuePair<string, string>>
{
private readonly MemoryConfigurationSource _source; /// <summary>
/// Initialize a new instance from the source.
/// </summary>
/// <param name="source">The source settings.</param>
public MemoryConfigurationProvider(MemoryConfigurationSource source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
} _source = source; if (_source.InitialData != null)
{
foreach (var pair in _source.InitialData)
{
Data.Add(pair.Key, pair.Value);
}
}
} /// <summary>
/// Add a new key and value pair.
/// </summary>
/// <param name="key">The configuration key.</param>
/// <param name="value">The configuration value.</param>
public void Add(string key, string value)
{
Data.Add(key, value);
}
}
MemoryConfigurationProvider 的构造函数完成了数据的初始化 。
到此为止,开头的那个栗子我们就梳理完了。
也许是我们开头那个例子过于简单了 , 写配置这块有点虎头蛇尾的感觉 。
我们再看一下主角 IConfigurationProvider , 发现其中定义的 Load 方法 ,我们还知道 ConfigurationProvider 是一个虚拟类 ,其 Load 方法还是个虚方法。
我们在翻一下源码看有没有其它类继承它呢 ? 如果有的话 ,它们是不是覆盖了 Load 方法呢?
果然我们发现了 不少 Provider , 如 JsonConfigurationProvider,XmlConfigurationProvider,EnvironmentVariablesConfigurationProvider 等。
为方便讲解,我们以EnvironmentVariablesConfigurationProvider 为例,并精简了代码。 Load 方法负责了负载配置的任务。男团其他成员也是如此。所以说 Provider们 实力担当。
public class EnvironmentVariablesConfigurationProvider : ConfigurationProvider
{
private readonly string _prefix; /// <summary>
/// Initializes a new instance.
/// </summary>
public EnvironmentVariablesConfigurationProvider() : this(string.Empty)
{ } /// <summary>
/// Initializes a new instance with the specified prefix.
/// </summary>
/// <param name="prefix">A prefix used to filter the environment variables.</param>
public EnvironmentVariablesConfigurationProvider(string prefix)
{
_prefix = prefix ?? string.Empty;
} /// <summary>
/// Loads the environment variables.
/// </summary>
public override void Load()
{
Load(Environment.GetEnvironmentVariables());
} internal void Load(IDictionary envVariables)
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var filteredEnvVariables = envVariables
.Cast<DictionaryEntry>()
.Where(entry => ((string)entry.Key).StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)); foreach (var envVariable in filteredEnvVariables)
{
var key = ((string)envVariable.Key).Substring(_prefix.Length);
data[key] = (string)envVariable.Value;
} Data = data;
}
}
但是 IConfigurationSource 呢 ,是不是可有可无 ?其实不是。IConfigurationSource 作用是提供原始配置,并将必要的信息传递给 Provider 。
我们现在大概知道 IConfigurationBuilder , IConfigurationProvider , IConfigurationRoot ,IConfigurationSource 之间的关系。我们总结一下,
1 各种配置原始数据( IConfigurationSource) 通过 IConfigurationProvider 转成键值对格式的配置数据
2 IConfigurationBuilder 内部维护多个 IConfigurationSource,IConfigurationBuilder 中的 Build方法 构建出 IConfigurationRoot
3 IConfigurationRoot 内部维护多个 IConfigurationProvider,并通过 IConfigurationProvider 读取配置数据
闯荡天下的天外流星全套剑谱奉上

限于篇幅 ,本篇介绍 配置读取 ,配置写入 2个知识点。
下一篇会介绍 配置变更监控 ,自定义配置 2个知识点。
感觉写的还凑合就点个赞吧
.Net Core 配置源码学习 (一)的更多相关文章
- ASP.NET Core MVC 源码学习:MVC 启动流程详解
前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...
- ASP.NET Core MVC 源码学习:Routing 路由
前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...
- ASP.NET Core MVC 源码学习:详解 Action 的激活
前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...
- ASP.NET Core MVC 源码学习:详解 Action 的匹配
前言 在 上一篇 文章中,我们已经学习了 ASP.NET Core MVC 的启动流程,那么 MVC 在启动了之后,当请求到达过来的时候,它是怎么样处理的呢? 又是怎么样把我们的请求准确的传达到我们的 ...
- 源码学习系列之SpringBoot自动配置(篇一)
源码学习系列之SpringBoot自动配置源码学习(篇一) ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属 ...
- SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的
系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...
- 【目录】Spring 源码学习
[目录]Spring 源码学习 jwfy 关注 2018.01.31 19:57* 字数 896 阅读 152评论 0喜欢 9 用来记录自己学习spring源码的一些心得和体会以及相关功能的实现原理, ...
- Asp.NetCore源码学习[2-1]:配置[Configuration]
Asp.NetCore源码学习[2-1]:配置[Configuration] 在Asp. NetCore中,配置系统支持不同的配置源(文件.环境变量等),虽然有多种的配置源,但是最终提供给系统使用的只 ...
- 源码学习系列之SpringBoot自动配置(篇二)
源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...
- Vue2.0源码学习(4) - 合并配置
合并配置 通过之前的源码学习,我们已经了解到了new Vue主要有两种场景,第一种就是在外部主动调用new Vue创建一个实例,第二个就是代码内部创建子组件的时候自行创建一个new Vue实例.但是无 ...
随机推荐
- Zabbix技术分享——使用docker-compose快速部署zabbix监控系统
前面文章有提到过使用docker来快速拉起一个zabbix监控系统(详见:如何使用docker快速部署一个zabbix监控系统),但是要一个个执行docker启动命令去将对应的容器启动.如果要配置参数 ...
- 【Shell脚本案例】案例6:查看网卡实时流量
一.背景 监控,对服务器查看实时流量 了解服务器的数据传输量 二.说明 1.获取网络流量 ifconfig查看网卡就能看到数据包传输情况 2.可以使用工具查看 iftop cat /proc/net/ ...
- Codeforces Round #838 (Div. 2) D. GCD Queries
题意 有个长度为n的排列p,[0,1,2,...n-1],你可以进行至多2*n次询问,每次询问两个i,j,返回gcd(pi,pj),让你在规定时间内猜出0在哪两个位置之一 思路 这是一道交互题,询问的 ...
- 命令指定IP端口号
tcping命令是针对tcp监控的,也可以看到ping值,即使源地址禁ping也可以通过tcping来监控服务器网络状态,除了简单的ping之外,tcping最大的一个特点就是可以指定端口. 将下载好 ...
- java中的数值运算
本文主要是掌握java中的整除和取模的运算: public class MathOperate { public static void main(String[] args) { // 取整运算 S ...
- des_招标
网站 aHR0cHM6Ly9jdGJwc3AuY29tLyMv 翻到第二页,加载了一个2,并且返回的都是加密的数据 点到initiator,可以看到发送的Axios请求,尝试全局搜索intercep ...
- 4、PageHelper分页查询
1.MyBatis的分页方式: 逻辑分页与物理分页 1.逻辑分页:使用MyBatis自带的RowBounds进行分页,是一次性查询很多数据,然后再在结果中检索分页的数据.这样做弊端是需要消耗大量的内存 ...
- 2021 & 2022年终回顾:山河无恙,烟火寻常
前言 又到了一年一度年终回顾的时候了,回想起去年年底圣诞节的时候由于忙着参加黑客松大赛,一下子就进入了新的一年,失去了年终回顾的动力,所以今年提前两个月开始进行回顾,这样的话今年最后一天就可以顺利发文 ...
- PostgreSQL(02): PostgreSQL常用命令
目录 PostgreSQL(01): Ubuntu20.04/22.04 PostgreSQL 安装配置记录 PostgreSQL(02): PostgreSQL常用命令 PostgreSQL 常用命 ...
- Java基础篇——注解和反射
注解 注解Annotation可以被其他程序(编译器)读取,常见的有@override 内置注解 @Override 适用于修饰方法,表明重写父类中的一个方法 @Deprecated 用于修饰类.方法 ...