• .Net Core配置Configuration源码研究


    最近又研究了一下.NetCore配置选项的源码实现,又学习到了不少东西。这篇文章先写一下IConfiguration的学习成果,Options的后面补上


    核心类

    ConfigurationBuilder:IConfigurationBuilder (构建IConfiguration)

    IConfigurationSource (配置数据来源)

    IConfigurationProvider (将配置源的原始结构转为为IDictionary<string, string>)

    ConfigurationRoot:IConfigurationRoot:IConfiguration (配置根节点)



    构建


    ConfigurationBuilder

    下面是ConfigurationBuilder中的主要代码

    可以看到ConfigurationBuilder的主要功能就是配置数据源到集合中

    在Build时依次调用IConfigurationSourceBuild函数,并将返回的IConfigurationProvider加入到List中

    最后用IConfigurationProvider的集合构建一个ConfigurationRoot对象

    
    public IList<IConfigurationSource> Sources = new List<IConfigurationSource>();
    
    public IConfigurationBuilder Add(IConfigurationSource source)
    {
        Sources.Add(source);
        return this;
    }
    
    public IConfigurationRoot Build()
    {
        List<IConfigurationProvider> list = new List<IConfigurationProvider>();
        foreach (IConfigurationSource source in Sources)
        {
            IConfigurationProvider item = source.Build(this);
            list.Add(item);
        }
    
        return new ConfigurationRoot(list);
    }
    
    

    IConfigurationSource

    public class EnvironmentVariablesConfigurationSource : IConfigurationSource
    {
        public string Prefix;
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new EnvironmentVariablesConfigurationProvider(Prefix);
        }
        public EnvironmentVariablesConfigurationSource()
        {
        }
    }
        
       
    public class CommandLineConfigurationSource : IConfigurationSource
    {
        public IDictionary<string, string> SwitchMappings;
        public IEnumerable<string> Args;
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new CommandLineConfigurationProvider(Args, SwitchMappings);
        }
        public CommandLineConfigurationSource()
        {
        }
    }
    
    //JsonConfigurationSource继承自FileConfigurationSource,我这里将其合为一个了
    public abstract class JsonConfigurationSource : IConfigurationSource
    {
    	public IFileProvider FileProvider { get; set; }
    	public string Path { get; set; }
    	public bool Optional { get; set; }
    	public bool ReloadOnChange { get; set; }
    	public int ReloadDelay { get; set; } = 250;
    
    	public Action<FileLoadExceptionContext> OnLoadException { get; set; }
        
    	public IConfigurationProvider Build(IConfigurationBuilder builder)
    	{
    		FileProvider = FileProvider ?? builder.GetFileProvider();
    		OnLoadException = OnLoadException ?? builder.GetFileLoadExceptionHandler();
    		return new JsonConfigurationProvider(this);
    	}
        
    	public void ResolveFileProvider()
    	{
    		if (FileProvider == null && !string.IsNullOrEmpty(Path) && System.IO.Path.IsPathRooted(Path))
    		{
    			string directoryName = System.IO.Path.GetDirectoryName(Path);
    			string text = System.IO.Path.GetFileName(Path);
    			while (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName))
    			{
    				text = System.IO.Path.Combine(System.IO.Path.GetFileName(directoryName), text);
    				directoryName = System.IO.Path.GetDirectoryName(directoryName);
    			}
    			if (Directory.Exists(directoryName))
    			{
    				FileProvider = new PhysicalFileProvider(directoryName);
    				Path = text;
    			}
    		}
    	}
    }
    
    

    上面展示了比较常用的三种ConfigurationSource,代码都比较简单。

    也很容易看出来ConfigurationSource的作用就是配置数据源,并不解析数据。

    解析数据源的功能由 IConfigurationProvider完成


    ConfigurationProvider

    下面为IConfigurationProvider接口定义的5个函数

    public interface IConfigurationProvider
    {
    	bool TryGet(string key, out string value);
    
    	void Set(string key, string value);
    
    	IChangeToken GetReloadToken();
    
    	void Load();
    
    	IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath);
    }
    

    ConfigurationProvider是一个抽象类,继承了IConfigurationProvider接口

    在新建Provider时一般都会选择直接继承ConfigurationProvider,接下来看一下ConfigurationProvider的几个核心方法

    public abstract class ConfigurationProvider : IConfigurationProvider
    {
    	private ConfigurationReloadToken _reloadToken = new ConfigurationReloadToken();
        
    	protected IDictionary<string, string> Data= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    
    	public virtual bool TryGet(string key, out string value)=>Data.TryGetValue(key, out value);
    
    	public virtual void Set(string key, string value)=>Data[key] = value;
    
    	public virtual void Load(){}
    
    	public IChangeToken GetReloadToken()
    	{
    		return _reloadToken;
    	}
    	
    	protected void OnReload()
    	{
    	ConfigurationReloadToken configurationReloadToken = Interlocked.Exchange(ref _reloadToken, new ConfigurationReloadToken());
    		configurationReloadToken.OnReload();
    	}
    

    可以推测出:

    • Load函数负责从源数据读取数据然后给字典Data赋值
    • ConfigurationProvider将数据存储在字典Data中,增加修改都是对字典的操作
    • 每个ConfigurationProvider都会生成一个IChangeToken,在OnReload函数被调用时生成新的Token,并调用原Token的OnReload函数

    ConfigurationRoot

    ConfigurationBuilderBuild函数中,我们生成了一个ConfigurationRoot,并给他传递了所有的ConfigrationProvider列表,下面我们看看他用我们的Provider都做了啥吧

    private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
    
    public ConfigurationRoot(IList<IConfigurationProvider> providers)
    {
        _providers = providers;
        _changeTokenRegistrations = new List<IDisposable>(providers.Count);
        foreach (IConfigurationProvider p in providers)
        {
            p.Load();
            ChangeToken.OnChange(p.GetReloadToken, 
               delegate{
    					var oldToken=Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
                   		oldToken.OnReload();
                    })
        }
    }
    
    public IChangeToken GetReloadToken()=>_changeToken;
    

    上面的代码也对部分地方进行了简化。可以看到ConfigurationRoot在生成时主要就做了两件事

    1. 调用Provider的Load函数,这会给ProviderData赋值
    2. 读取ProviderReloadToken,每个ProviderReload事件都会触发ConfigurationRoot自己的ReloadTokenReload事件

    至此配置的数据源构建这块就分析完啦!



    查询

    常规的配置查询有两种基本方式 :索引器GetSection(string key)

    其余的GetValue等等都是一些扩展方法,本篇文章不对此进行展开研究


    索引器

    索引器的查询执行的方式是倒叙查询所有的Provider,然后调用Provider的TryGet函数,在查询时重名的Key,最后加入的会生效。

    赋值则是依次调用每个Provider的Set函数

    public string this[string key]
    {
    	get
    	{
    		for (int num = _providers.Count - 1; num >= 0; num--)
    		{
    			if (_providers[num].TryGet(key, out var value))
    			{
    				return value;
    			}
    		}
    		return null;
    	}
    	set
    	{
    		foreach (IConfigurationProvider provider in _providers)
    		{
    			provider.Set(key, value);
    		}
    	}
    }
    

    GetSection

    public IConfigurationSection GetSection(string key)
    {
    	return new ConfigurationSection(this, key);
    }
    
    public class ConfigurationSection : IConfigurationSection, IConfiguration
    {
    	private readonly IConfigurationRoot _root;
    	private readonly string _path;
    	private string _key;
    	public string Value
    	{
    		get
    		{
    			return _root[Path];
    		}
    		set
    		{
    			_root[Path] = value;
    		}
    	}
    	
        //ConfigurationPath.Combine = string.Join(":",paramList);
    	public string this[string key]
    	{
    		get
    		{
    			return _root[ConfigurationPath.Combine(Path, key)];
    		}
    		set
    		{
    			_root[ConfigurationPath.Combine(Path, key)] = value;
    		}
    	}
    
    	public ConfigurationSection(IConfigurationRoot root, string path)
    	{
    		_root = root;
    		_path = path;
    	}
    
    	public IConfigurationSection GetSection(string key)
    	{
    		return _root.GetSection(ConfigurationPath.Combine(Path, key));
    	}
    
    	public IEnumerable<IConfigurationSection> GetChildren()
    	{
    		return _root.GetChildrenImplementation(Path);
    	}
    
    	public IChangeToken GetReloadToken()
    	{
    		return _root.GetReloadToken();
    	}
    }
    
    

    可以看到GetSection会生成一个ConfigurationSection对象

    ConfigurationSection在读取/设置值时实际上就是对查询的Key用:拼接,然后调用IConfigurationRoot(_root)的赋值或查询函数

    关于Configuration的配置和读取的知识点大概就是以上这些了,还有更深入的涉及到对象的绑定这一块Get<> Bind<> GetChildren()等,比较难读,要一行一行代码看,以后有时间可能再研究一下

    最后贴上一个从数据加载配置源并动态更新的小例子



    DBConfiguration示例

     public void Run()
     {
         var builder = new ConfigurationBuilder();
         var dataProvider = new DBDataProvider();
         builder.Sources.Add(new DBConfigurationSource() { DataProvider = dataProvider, ReloadOnChange = true, Table = "config" });
         IConfigurationRoot config = builder.Build();
    
         Console.WriteLine(config["time"]);
         Task.Run(() =>
                  {
                      while (true)
                      {
                          Thread.Sleep(2000);
                          dataProvider.Update("config");
                          Console.WriteLine($"读取配置时间:{config["time"]}");
                      }
                  });
         Thread.Sleep(20000);
     }
    
    public class DBConfigurationProvider : ConfigurationProvider
    {
        private DBConfigurationSource Source { get; }
        public DBConfigurationProvider(DBConfigurationSource source)
        {
            Source = source;
        }
    
        public override void Load()
        {
            if (Source.ReloadOnChange)
            {
                ChangeToken.OnChange(() => Source.DataProvider.Watch(Source.Table), LoadData);
            }
            LoadData();
        }
    
        private void LoadData()
        {
            var data = Source.DataProvider.GetData(Source.Table);
            Load(data);
            OnReload();
        }
    
        public void Load(Dictionary<string, object> data)
        {
            var dic = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (var element in data)
            {
                dic.Add(element.Key, element.Value?.ToString());
            }
            base.Data = dic;
        }
    }
    
    public class DBConfigurationSource : IConfigurationSource
    {
        public DBDataProvider DataProvider { get; set; }
        public string Table { get; set; }
        public bool ReloadOnChange { get; set; }
        public bool Optional { get; set; }
    
        public DBConfigurationSource()
        {
        }
    
        public IConfigurationProvider Build(IConfigurationBuilder builder)
        {
            return new DBConfigurationProvider(this);
        }
    }
    
    public class DBDataProvider
    {
        private ConcurrentDictionary<string, CancellationTokenSource> tableToken = new ConcurrentDictionary<string, CancellationTokenSource>();
        public DBDataProvider()
        {
        }
    
        public Dictionary<string, object> GetData(string table)
        {
            switch (table)
            {
                case "config":
                    return GetConfig();
            }
            return new Dictionary<string, object>();
        }
    
        public void Update(string table)
        {
            Console.WriteLine($"更新数据库数据table:{table}");
            if (tableToken.TryGetValue(table, out CancellationTokenSource cts))
            {
                var oldCts = cts;
                tableToken[table] = new CancellationTokenSource();
                oldCts.Cancel();
            }
        }
    
        private Dictionary<string, object> GetConfig()
        {
            var valueDic = new Dictionary<string, object>();
            valueDic.TryAdd("time", DateTime.Now.ToString());
            valueDic.TryAdd("weather", "windy");
            valueDic.TryAdd("people_number:male", 100);
            valueDic.TryAdd("people_number:female", 150);
            return valueDic;
        }
    
        public IChangeToken Watch(string table)
        {
            var cts = tableToken.GetOrAdd(table, x => new CancellationTokenSource());
            return new CancellationChangeToken(cts.Token);
        }
    }
    
  • 相关阅读:
    ECMAScript——引用数据类型之date
    ECMAScript——引用数据类型之RegExp
    ECMAScript——引用数据类型之array
    ECMAScript——引用数据类型之object
    ECMAScript——基本数据类型之null和undefined
    ECMAScript——基本数据类型之boolean
    ECMAScript——基本数据类型之string
    ECMAScript——基本数据类型之number
    libgdx 3D Bullet 碰撞检测二
    libgdx 3D Bullet 碰撞检测一
  • 原文地址:https://www.cnblogs.com/bluesummer/p/15236098.html
Copyright © 2020-2023  润新知