• 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);
            }
  • 相关阅读:
    idea主题更换pycharm/intellij
    随机生成n张扑克牌。
    JAVA生成6个1-8的随机数,要求无重复。
    一道简单 的循环
    linux虚拟机互访
    linux中grep命令
    vi和vim编辑器
    文件压缩打包以及备份
    文件内容查询
    目录相关操作
  • 原文地址:https://www.cnblogs.com/lanpingwang/p/12536886.html
Copyright © 2020-2023  润新知