netcore3.0 IConfiguration配置源码解析(一)
配置的实现在以Microsoft.Extensions.Configuration开头的Nuget包中,引入对应的nuget包就可以使用配置功能
Github地址:https://github.com/dotnet/extensions/tree/master/src/Configuration
一、先看下下面接口的关系图
二、各个接口的实现
1、IConfiguration和IConfigurationRoot的实现类:ConfigurationRoot
ConfigurationRoot的构造函数接收IList<IConfigurationProvider>类型,遍历provider调用Load()方法加载数据
1 public ConfigurationRoot(IList<IConfigurationProvider> providers) 2 { 3 if (providers == null) 4 { 5 throw new ArgumentNullException(nameof(providers)); 6 } 7 8 _providers = providers; 9 _changeTokenRegistrations = new List<IDisposable>(providers.Count); 10 foreach (var p in providers) 11 { 12 p.Load(); 13 _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged())); 14 } 15 }
实现IConfiguration的索引器如下:
遍历每个provider,从provider获取对应键的值返回,在这里可以看出,键值对都是保存在各个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 { if (!_providers.Any()) { throw new InvalidOperationException(Resources.Error_NoSources); } foreach (var provider in _providers) { provider.Set(key, value); } } }
IConfiguration的GetChildren实现如下
public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path) { return root.Providers .Aggregate(Enumerable.Empty<string>(), (seed, source) => source.GetChildKeys(seed, path)) .Distinct(StringComparer.OrdinalIgnoreCase) .Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); }
IConfiguration的GetReloadToken实现如下:
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); public IChangeToken GetReloadToken() => _changeToken;
IChangeToken 的实现类:
public class ConfigurationReloadToken : IChangeToken { private CancellationTokenSource _cts = new CancellationTokenSource(); /// <summary> /// Indicates if this token will proactively raise callbacks. Callbacks are still guaranteed to be invoked, eventually. /// </summary> /// <returns>True if the token will proactively raise callbacks.</returns> public bool ActiveChangeCallbacks => true; /// <summary> /// Gets a value that indicates if a change has occurred. /// </summary> /// <returns>True if a change has occurred.</returns> public bool HasChanged => _cts.IsCancellationRequested; /// <summary> /// Registers for a callback that will be invoked when the entry has changed. <see cref="Microsoft.Extensions.Primitives.IChangeToken.HasChanged"/> /// MUST be set before the callback is invoked. /// </summary> /// <param name="callback">The callback to invoke.</param> /// <param name="state">State to be passed into the callback.</param> /// <returns>The <see cref="CancellationToken"/> registration.</returns> public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _cts.Token.Register(callback, state); /// <summary> /// Used to trigger the change token when a reload occurs. /// </summary> public void OnReload() => _cts.Cancel(); }
调用IConfiguration的GetReloadToken返回的IChangeToken,可以调用RegisterChangeCallback注册一些回调方法,当配置发生改变时会触发这些回调方法
IConfigurationRoot的Reload方法实现如下
public void Reload() { foreach (var provider in _providers) { provider.Load(); } RaiseChanged(); }
该方法会调用每个provider的Load方法重新加载配置信息,并调用IChangeToken的OnReload方法
2、IConfigurationSection的实现类ConfigurationSection
ConfigurationSection可以理解为针对指定配置节点下的配置信息,对IConfigurationRoot 进行相关的调用
public class ConfigurationSection : IConfigurationSection { private readonly IConfigurationRoot _root; private readonly string _path; private string _key; /// <summary> /// Initializes a new instance. /// </summary> /// <param name="root">The configuration root.</param> /// <param name="path">The path to this section.</param> public ConfigurationSection(IConfigurationRoot root, string path) { if (root == null) { throw new ArgumentNullException(nameof(root)); } if (path == null) { throw new ArgumentNullException(nameof(path)); } _root = root; _path = path; } /// <summary> /// Gets the full path to this section from the <see cref="IConfigurationRoot"/>. /// </summary> public string Path => _path; /// <summary> /// Gets the key this section occupies in its parent. /// </summary> public string Key { get { if (_key == null) { // Key is calculated lazily as last portion of Path _key = ConfigurationPath.GetSectionKey(_path); } return _key; } } /// <summary> /// Gets or sets the section value. /// </summary> public string Value { get { return _root[Path]; } set { _root[Path] = value; } } /// <summary> /// Gets or sets the value corresponding to a configuration key. /// </summary> /// <param name="key">The configuration key.</param> /// <returns>The configuration value.</returns> public string this[string key] { get { return _root[ConfigurationPath.Combine(Path, key)]; } set { _root[ConfigurationPath.Combine(Path, key)] = value; } } /// <summary> /// Gets a configuration sub-section with the specified key. /// </summary> /// <param name="key">The key of the configuration section.</param> /// <returns>The <see cref="IConfigurationSection"/>.</returns> /// <remarks> /// This method will never return <c>null</c>. If no matching sub-section is found with the specified key, /// an empty <see cref="IConfigurationSection"/> will be returned. /// </remarks> public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key)); /// <summary> /// Gets the immediate descendant configuration sub-sections. /// </summary> /// <returns>The configuration sub-sections.</returns> public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path); /// <summary> /// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded. /// </summary> /// <returns>The <see cref="IChangeToken"/>.</returns> public IChangeToken GetReloadToken() => _root.GetReloadToken(); }
3、 IConfigurationProvider的抽象类ConfigurationProvider
ConfigurationProvider内部定义了一个类型为Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)的Data属性用来保存配置信息,IConfigurationProvider接口的TryGet和Set方法针对Data进行获取、设置配置
ConfigurationRoot类获取配置信息类获取或设置配置信息的的时候就是调用TryGet和Set方法
public abstract class ConfigurationProvider : IConfigurationProvider { private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken(); /// <summary> /// Initializes a new <see cref="IConfigurationProvider"/> /// </summary> protected ConfigurationProvider() { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } /// <summary> /// The configuration key value pairs for this provider. /// </summary> protected IDictionary<string, string> Data { get; set; } /// <summary> /// Attempts to find a value with the given key, returns true if one is found, false otherwise. /// </summary> /// <param name="key">The key to lookup.</param> /// <param name="value">The value found at key if one is found.</param> /// <returns>True if key has a value, false otherwise.</returns> public virtual bool TryGet(string key, out string value) => Data.TryGetValue(key, out value); /// <summary> /// Sets a value for a given key. /// </summary> /// <param name="key">The configuration key to set.</param> /// <param name="value">The value to set.</param> public virtual void Set(string key, string value) => Data[key] = value; /// <summary> /// Loads (or reloads) the data for this provider. /// </summary> public virtual void Load() { } /// <summary> /// Returns the list of keys that this provider has. /// </summary> /// <param name="earlierKeys">The earlier keys that other providers contain.</param> /// <param name="parentPath">The path for the parent IConfiguration.</param> /// <returns>The list of keys for this provider.</returns> public virtual IEnumerable<string> GetChildKeys( IEnumerable<string> earlierKeys, string parentPath) { var prefix = parentPath == null ? string.Empty : parentPath + ConfigurationPath.KeyDelimiter; return Data .Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) .Select(kv => Segment(kv.Key, prefix.Length)) .Concat(earlierKeys) .OrderBy(k => k, ConfigurationKeyComparer.Instance); } private static string Segment(string key, int prefixLength) { var indexOf = key.IndexOf(ConfigurationPath.KeyDelimiter, prefixLength, StringComparison.OrdinalIgnoreCase); return indexOf < 0 ? key.Substring(prefixLength) : key.Substring(prefixLength, indexOf - prefixLength); } /// <summary> /// Returns a <see cref="IChangeToken"/> that can be used to listen when this provider is reloaded. /// </summary> /// <returns>The <see cref="IChangeToken"/>.</returns> public IChangeToken GetReloadToken() { return _reloadToken; } /// <summary> /// Triggers the reload change token and creates a new one. /// </summary> protected void OnReload() { var previousToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken()); previousToken.OnReload(); } /// <summary> /// Generates a string representing this provider name and relevant details. /// </summary> /// <returns> The configuration name. </returns> public override string ToString() => $"{GetType().Name}"; }
实现了IConfigurationSource接口的类会Build一个IConfigurationProvider
4、IConfigurationBuilder接口实现类ConfigurationBuilder
public class ConfigurationBuilder : IConfigurationBuilder { /// <summary> /// Returns the sources used to obtain configuration values. /// </summary> public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); /// <summary> /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/> /// and the registered <see cref="IConfigurationProvider"/>s. /// </summary> public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>(); /// <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); } }
该类是创建配置的核心,其内部定义了类型为List<IConfigurationSource>的Source属性
调用Build方法时,会遍历Source中的每个IConfigurationProvider,从而构建ConfigurationRoot对象
当我们创建ConfigurationBuilder对象时,需要向Source添加对应的IConfigurationSource,IConfigurationBuilder的扩展方法其实就是添加对应的IConfigurationSource
5、 我们看下IConfigurationSource、IConfigurationProvider接口的几个实现类
ChainedConfigurationSource和ChainedConfigurationProvider
ChainedConfigurationSource有个IConfiguration接口的Configuration属性,当ChainedConfigurationProvider调用TryGet、Set时,其实调用了IConfiguration
ChainedConfigurationSource可以理解为IConfiguration接口的封装
public static class ChainedBuilderExtensions { /// <summary> /// Adds an existing configuration to <paramref name="configurationBuilder"/>. /// </summary> /// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="config">The <see cref="IConfiguration"/> to add.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder configurationBuilder, IConfiguration config) => AddConfiguration(configurationBuilder, config, shouldDisposeConfiguration: false); /// <summary> /// Adds an existing configuration to <paramref name="configurationBuilder"/>. /// </summary> /// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="config">The <see cref="IConfiguration"/> to add.</param> /// <param name="shouldDisposeConfiguration">Whether the configuration should get disposed when the configuration provider is disposed.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddConfiguration(this IConfigurationBuilder configurationBuilder, IConfiguration config, bool shouldDisposeConfiguration) { if (configurationBuilder == null) { throw new ArgumentNullException(nameof(configurationBuilder)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } configurationBuilder.Add(new ChainedConfigurationSource { Configuration = config, ShouldDisposeConfiguration = shouldDisposeConfiguration, }); return configurationBuilder; } }
IConfigurationBuilder 的扩展方法AddConfiguration也就是添加ChainedConfigurationSource对象
MemoryConfigurationSource和MemoryConfigurationProvider
MemoryConfigurationSource有类型为IEnumerable<KeyValuePair<string, string>>的InitialData属性
MemoryConfigurationProvider会把InitialData的键值对信息添加到基类ConfigurationProvider的Data属性中
核心代码:
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); } } }
扩展方法:
public static class MemoryConfigurationBuilderExtensions { /// <summary> /// Adds the memory configuration provider to <paramref name="configurationBuilder"/>. /// </summary> /// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddInMemoryCollection(this IConfigurationBuilder configurationBuilder) { if (configurationBuilder == null) { throw new ArgumentNullException(nameof(configurationBuilder)); } configurationBuilder.Add(new MemoryConfigurationSource()); return configurationBuilder; } /// <summary> /// Adds the memory configuration provider to <paramref name="configurationBuilder"/>. /// </summary> /// <param name="configurationBuilder">The <see cref="IConfigurationBuilder"/> to add to.</param> /// <param name="initialData">The data to add to memory configuration provider.</param> /// <returns>The <see cref="IConfigurationBuilder"/>.</returns> public static IConfigurationBuilder AddInMemoryCollection( this IConfigurationBuilder configurationBuilder, IEnumerable<KeyValuePair<string, string>> initialData) { if (configurationBuilder == null) { throw new ArgumentNullException(nameof(configurationBuilder)); } configurationBuilder.Add(new MemoryConfigurationSource { InitialData = initialData }); return configurationBuilder; } }
调用AddInMemoryCollection扩展方法时,我们可以传IEnumerable<KeyValuePair<string, string>>类型的配置数据
StreamConfigurationSource和StreamConfigurationProvider
StreamConfigurationSource和StreamConfigurationProvider都是抽象类,定义简单
public abstract class StreamConfigurationSource : IConfigurationSource { /// <summary> /// The stream containing the configuration data. /// </summary> public Stream Stream { get; set; } /// <summary> /// Builds the <see cref="StreamConfigurationProvider"/> for this source. /// </summary> /// <param name="builder">The <see cref="IConfigurationBuilder"/>.</param> /// <returns>An <see cref="IConfigurationProvider"/></returns> public abstract IConfigurationProvider Build(IConfigurationBuilder builder); }
public abstract class StreamConfigurationProvider : ConfigurationProvider { /// <summary> /// The source settings for this provider. /// </summary> public StreamConfigurationSource Source { get; } private bool _loaded; /// <summary> /// Constructor. /// </summary> /// <param name="source">The source.</param> public StreamConfigurationProvider(StreamConfigurationSource source) { Source = source ?? throw new ArgumentNullException(nameof(source)); } /// <summary> /// Load the configuration data from the stream. /// </summary> /// <param name="stream">The data stream.</param> public abstract void Load(Stream stream); /// <summary> /// Load the configuration data from the stream. Will throw after the first call. /// </summary> public override void Load() { if (_loaded) { throw new InvalidOperationException("StreamConfigurationProviders cannot be loaded more than once."); } Load(Source.Stream); _loaded = true; } }
可以看到Provider里面有个Load(Stream stream)方法,其实该方法是从流中获取配置信息,比如读取json文件流,具体的实现类后面再讲
总结:本文主要分析了netcore配置系统,我们其实在项目里一般都是注入IConfiguration接口就能获取对应的配置信息,这里涉及到了netcore的依赖注入
本质上是系统先创建ConfigurationBuilder对象,再调用对应的扩展方法向Sources添加配置数据源,最后Build出ConfigurationRoot对象,把ConfigurationRoot注入到容器中,所有的配置数据其实从ConfigurationRoot对象中获取的
主要核心代码:
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); }