首页 > 作文

.NET6 ConfigurationManager的实现及使用方式

更新时间:2023-04-04 12:21:11 阅读: 评论:0

前言

友情提示:建议阅读本文之前先了解下.net core配置体系相关,也可以参考本人之前的文章《.net core configuration源码探究 》然后对.net core的configuration体系有一定的了解,使得理解起来更清晰。

在.net6中关于配置相关多出一个关于配置相关的类configurationmanager,如果大概了解过minimal api中的webapplicationbuilder类相信你肯定发现了,在minimal api中的配置相关属性configuration正是configurationmanager的对象。configurationmanager本身并没有引入新的技术,也不是一个体系,只是在原来的基础上进行了进一步的封装,使得配置体系有了一个新的外观操作,暂且可以理解为新瓶装旧酒。本文我们就来了解下configurationmanager类,来看下微软为何在.net6中会引入这么一个新的操作。

使用方式

关于.net6中configurationmanager的使用方式,我们先通过简单的示例演示一下

configurationmanager configurationmanager = new();configurationmanager.addjsonfile("appttings.json",true,reloadonchange:true);string rvicename = configurationmanager["rvicename"];console.writeline(rvicename);

当然,关于获取值得其他方式。比如getction、getchildren相关方法还是可以继续使用的,或者使用binder扩展包相关的get<string>()getvalue<nacosoptions>("nacos")类似的方法也照样可以使用。那它和之前的.net core上的配置使用起来有什么不一样呢,我们看一下之前配置相关的使用方式,如下所示

iconfigurationbuilder configurationbuilder = new configurationbuilder().addjsonfile("appttings.json");iconfiguration configuration = configurationbuilder.build();string rvicename = configuration["rvicename"];console.writeline(rvicename);

这里需要注意的是,如果你是使用configurationmanager或者是iconfiguration封装的helper类相关,并没有通过框架体系默认注入的时候,一定要注意将其设置为单例模式。其实这个很好理解,先不说每次用的时候都去实例化带来的内存cpu啥的三高问题。读取配置文件本质不就是把数据读到内存中吗?内存中有一份缓存这就好了,每次都去重新实例去读本身就是一种不规范的方式。许多时候如果你实在不知道该定义成什么样的生命周期,可以参考微软的实现方式,以configurationmanager为例,我们可以参考webapplicationbuilder类中对configurationmanager注册的生命周期[点击查看源码]

public configurationmanager configuration { get; } = new();//这里注册为了单例模式rvices.addsingleton<iconfiguration>(_ => configuration);

通过上面我们演示的示例可以看出在configurationmanager的时候注册配置和读取配置相关都只是使用了这一个类。而在之前的配置体系中,注册配置需要使用iconfigurationbuilder,然后通过build方法得到iconfiguration实例,然后读取是通过iconfiguration实例进行的。本身操作配置的时候iconfigurationbuilder和iconfiguration是满足单一职责原则没问题,像读取配置这种基础操作,应该是越简单越好,所以微软才进一步封装了configurationmanager来简化配置相关的操作。

在.net6中微软并没有放弃iconfigurationbuilder和iconfiguration,因为这是操作配置文件的基础类,微软只是借助了它们两个在上面做了进一层封装而已,这个是需要我们了解的。

源码探究

上面我们了解了新的configurationmanager的使用方式,这里其实我们有疑问了,为什么configurationmanager可以进行注册和读取操作。上面我提到过configurationmanager本身就是新瓶装旧酒,而且它只是针对原有的配置体系做了一个新的外观,接下来哦我们就从源码入手,看一下它的实现方式。

定义入手

首先来看一下configurationmanager的的定义,如下所示[点击查看源码]

public aled class configurationmanager : iconfigurationbuilder, iconfigurationroot, idisposable{}

其实只看它的定义就可以解答我奇观们心中的大部分疑惑了,之所以configurat辟谷减肥具体方法ionmanager能够满足iconfigurationbuilder和iconfigurationroot这两个操作的功能是因为它本身就是实现了这两个接口,集它们的功能于一身了,iconfigurationroot接口本身就集成自iconfiguration接口。因此如果给configurationmanager换个马甲的话你就会发现还是原来的配方还是原来的味道

configurationmanager configurationmanager = new();iconfigurationbuilder configurationbuilder = configurationmanager.addjsonfile("appttings.json", true, reloadonchange: true);//尽管放心的调用build完全不影响啥iconfiguration configuration = configurationbuilder.build();string rvicename = configuration["rvicename"];console.writeline(rvicename);

这种写法只是为了更好的看清它的本质,如果真实操作这么写,确实有点画蛇添足了,因为configurationmanager本身就是为了简化我们的操作。

认识iconfigurationbuilder和iconfiguration

通过上面我们了解到configurationmanager可以直接注册过配置文件就可以直接去操作配置文件里的内容,这一步是肯定通过转换得到的,毕竟之前的方式我们是通过iconfigurationbuilder的build操作得到的iconfiguration的实例,那么我们就先来看下原始的方式是如何实现的。这里需要从iconfigurationbuilder的默认实现类configurationbuilder说起,它的实现很简单[点击查看源码]

public class configurationbuilder : iconfigurationbuilder{    /// <summary>    /// 添加的数据源被存放到了这里    /// </summary>    public ilist<iconfigurationsource> sources { get; } = new list<iconfigurationsource>();    public idictionary<string, object> properties { get; } = new dictionary<string, object>();    /// <summary>    /// 添加ic热点事件onfigurationsource数据源    /// </summary>    /// <returns></returns>    public iconfigurationbuilder add(iconfigurationsource source)    {        if (source == null)        {            throw new argumentnullexception(nameof(source));        }        sources.add(source);        return this;    }    public iconfigurationroot build()    {        //获取所有添加的iconfigurationsource里的iconfigurationprovider        var providers = new list<iconfigurationprovider>();        foreach (var source in sources)        {            var provider = source.build(this);            providers.add(provider);        }        //用providers去实例化configurationroot        return new configurationroot(providers);    }}

这里我们来解释一下,其实我们注册配置相关的时候比如addjsonfile()、addenvironmentvariables()、addinmemorycollection()等等它们其实都是扩展方法,本质就是添加iconfigurationsource实例,而iconfigurationbuilder的build本质操作其实就是在iconfigurationsource集合中得到iconfigurationprovider集合,因真正从配置读取到的数据都是包含在iconfigurationprovider实例中的,configurationroot通过一系列的封装,让我们可以更便捷的得到配置里相关的信息。这就是configurationbuilder的工作方式,也是配置体系的核心原理。
我们既然知道了添加配置的本质其实就是iconfigurationbuilder.add(iconfigurationsource source)那么我就来看一下configurationmanager是如何实现这一步的。我们知道configurationmanager实现了iconfigurationbuilder接口,所以必然重写了iconfigurationbuilder的add方法,找到源码位置[点击查看源码]

private readonly configurationsources _sources = new configurationsources(this); ;iconfigurationbuilder iconfigurationbuilder.add(iconfigurationsource source){    _sources.add(source ?? throw new argumentnullexception(nameof(source)));    return this;}

这里返回了this也就是当前configurationmanager实例是为了可以进行链式编程,configurationsources这个类是个新物种,原来的类叫configurationsource,这里多了个s表明了这是一个集合类,我们就来看看它是个啥操作,找到源码位置[点击查看源码]

/// <summary>/// 本身是一个iconfigurationsource集合/// </summary>private class configurationsources : ilist<iconfigurationsource>{    private readonly list<iconfigurationsource> _sources = new();    private readonly configurationmanager _config;    /// <summary>    /// 因为是configurationmanager的内部类所以传递了当前configurationmanager实例    /// </summary>    /// <param name="config"></param>    public configurationsources(configurationmanager config)    {        _config = config;    }    /// <summary>    /// 根据索引获取其中一个iconfigurationsource实例    /// </summary>    /// <returns></returns>    public iconfigurationsource this[int index]    {        get => _sources[index];        t        {            _sources[index] = value;            _config.reloadsources();        }    }    public int count => _sources.count;    public bool isreadonly => fal;    /// <summary>    /// 这是重点添加配置源    /// </summary>    /// <param name="source"></param>    public void add(iconfigurationsource source)    {        //给自己的iconfigurationsource集合添加        _sources.add(source);        //调用了configurationmanager的addsource方法        _config.addsource(source);    }    /// <summary>    /// 实现ilist清除操作    /// </summary>    public void clear()    {        _sources.clear();        //这里可以看到configurationmanager的reloadsources方法很重要        //通过名字可以看出是刷新配置数据用的        _config.reloadsources();    }    public void inrt(int index, iconfigurationsource source)    {        _sources.inrt(index, source);        _config.reloadsources();    }    public bool remove(iconfigurationsource source)    {        var removed = _sources.remove(source);        _config.reloadsources();        return removed;    }    public void removeat(int index)    {        _sources.removeat(index);        _config.reloadsources();    }    //这里省略了实现了实现ilist接口的其他操作    //configurationsources本身就是ilist<iconfigurationsource>}

正如我们看到的那样configurationsources本身就是一个iconfigurationsource的集合,在新的.net体系中微软喜欢把集合相关的操作封装一个collection类,这样的好处就是让大家能更清晰的了解它是功能实现类,而不在用一个数据结构的眼光去看待。通过源码我们还看到了add方法里还调用了configurationmanager的addsource方法,这究竟是一个什么操作我们来看下[点击查看源码]

private readonly object _providerlock = new();private readonly list<iconfigurationprovider> _providers = new();private readonly list<idisposable> _changetokenregistrations = new();private void addsource(iconfigurationsource source){    lock (_providerlock)    {        //在iconfigurationsource中得到iconfigurationprovider实例        var provider = source.build(this);        //添加到_providers集合中        //我们提到过从配置源得到的配置都是通过iconfigurationprovider得到的        _providers.add(provider);        //iconfigurationprovider的load方法是从配置源中得到配置数据加载到程序内存中        provider.load();        //注册更改令牌操作,使得配置可以进行动态刷新加载        _changetokenregistrations.add(changetoken.onchange(() => provider.getreloadtoken(), () => raichanged()));    }    //添加新的配置源要刷新令牌操作    raichanged();}private configurationreloadtoken _changetoken = new();private void raichanged(){    //每次对配置源进行更改操作需要得到新的更改令牌实例,用于可重复通知配置变更相关    var previoustoken = interlocked.exchange(ref _changetoken, new configurationreloadtoken());    previoustoken.onreload();}

从上面的configurationsources方法里我们可以看到动态的针对configurationsources里的configurationsource进行更改会每次都调用reloadfeaturessources方法,我们来看一下它的实现[点击查看源码]

private readonly object _providerlock = new();private void reloadsources(){    lock (_providerlock)    {        //释放原有操作        disporegistrationsandprovidersunsynchronized();        //清除更改令牌        _changetokenregistrations.clear();        //清除_providers        _providers.clear();        //重新加载_providers        foreach (var source in _sources)        {            _providers.add(source.build(this));        }        //重新加载数据添加通知令牌        foreach (var p in _providers)        {            p.load();            _changetokenregistrations.add(changetoken.onchange(() => p.getreloadtoken(), () => raichanged()));        }    }    raichanged();}

这个方法几乎是重新清除了原来的操作,然后完全的重新加载一遍数据,理论上来说是一个低性能的操作,不建议频繁使用。还有因为configurationmanager实现了iconfigurationbuilder接口所以也必然实现了它的build方法少不了,看一下它的实现[点击查看源码]

iconfigurationroot iconfigurationbuilder.build() => this;

这波操作真的很真的很骚气,我即是iconfigurationroot我也是iconfigurationbuilder,反正操作都是我自己,所以这里你可劲的build也不影响啥,反正得到的也都是一个configurationmanager实例。到了这里结合我们之前了解到的传统的iconfigurationbuilder和iconfiguration关系,以及我们上面展示的展示的configurationsources类的实现和configurationmanager的addsource方法。其实我们可以发现我们上面展示的configurationmanager类的相关操作其实就是实现了之前configurationbuilder类里的操作。其实这里微软可以不用实现configurationsources类完全基于configurationbuilder也能实现一套,但是显然微软没这么做,具体想法咱们不得而知,估计是只想以来抽象,而并不像以来原来的实现方式吧。

我们上面展示的这一部分的configurationmanager代码,其实就是替代了原来的configurationbuilder类的功能。

读取操作

上面我们看到了在configurationmanager中关于以前configurationmanager类的实现。接下来我们看一下读取相关的操作,即在这里configurationmanager成为了iconfiguration实例,所以我们先来看下iconfiguration接口的定义[点击查看源码]

public interface iconfiguration{    /// <summary>    /// 通过配置名称获取值    /// </summary>    /// <returns></returns>    string this[string key] { get; t; }    /// <summary>    /// 获取一个配置节点    /// </summary>    /// <returns></returns>    iconfigurationction getction(string key);    /// <summary>    /// 获取所有子节点    /// </summary>    /// <returns></returns>    ienumerable<iconfigurationction> getchildren();    /// <summary>    /// 刷新数据通知    /// </summary>    /// <returns></returns>    ichangetoken getreloadtoken();}

通过代码我们看到了iconfiguration的定义,也就是在configurationmanager类中必然也实现也这几个操作,首先便是通过索引器直接根据配置的名称获取值得操作[点击查看源码]

private readonly object _providerlock = new();private readonly list<iconfigurationprovider> _providers = new();/// <summary>/// 可读可写的操作/// </summary>/// <returns></returns>public string this[string key]{    get    {        lock (_providerlock)        {            //通过在iconfigurationprovider集合中获取配置值            return configurationroot.getconfiguration(_providers, key);        }    }    t    {        lock (_provide心旷神怡的意思是什么rlock)        {            //也可以把值放到iconfigurationprovider集合中            configurationroot.tconfiguration(_providers, key, value);        }    }}

其中_providers中的值是我们在addsource方法中添加进来的,这里的本质其实还是针对configurationroot做了封装。configurationroot实现了iconfigurationroot接口,iconfigurationroot实现了iconfiguration接口。而configurationroot的getconfiguration方法和tconfiguration是最直观体现configurationroot本质就是iconfigurationprovider包装的证据。我们来看一下configurationroot这两个方法的实现[点击查看源码]

internal static string getconfiguration(ilist<iconfigurationprovider> providers, string key){    //倒序遍历providers,因为configuration采用的后来者居上的方式,即后注册的key会覆盖先前注册的key    for (int i = providers.count - 1; i >= 0; i--)    {        iconfigurationprovider provider = providers[i];        //如果找到key的值就直接返回        if (provider.tryget(key, out string value))        {            return value;        }    }    return null;}internal static void tconfiguration(ilist<iconfigurationprovider> providers, string key, string value){    if (providers.count == 0)    {        throw new invalidoperationexception("");    }    //给每个provider都t这个键值,虽然浪费了一部分内存,但是可以最快的获取    foreach (iconfigurationprovider provider in providers)    {        provider.t(key, value);    }}

关于getction的方法实现,本质上是返回configurationction实例,configurationction本身也是实现了iconfiguration接口,所有关于配置获取的操作出口都是面向iconfiguration的。

public iconfigurationction getction(string key) => new configurationction(this, key);

getchildren方法是获取配置的所有子节点的操作,本质是返回iconfigurationction的集合,实现方式如如下

private readonly object _providerlock = new();public ienumerable<iconfigurationction> getchildren(){    lock (_providerlock)    {        //调用了getchildrenimplementation方法        return this.getchildrenimplementation(null).tolist();    }}

这里调用了getchildrenimplementation方法,而getchildrenimplementation是一个扩展方法,我们来看一下它的实现[点击查看源码]

internal static ienumerable<iconfigurationction> getchildrenimplementation(this iconfigurationroot root, string path){    //在当前configurationmanager实例中获取到所有的iconfigurationprovider实例    //然后包装成iconfigurationction集合    return root.providers        .aggregate(enumerable.empty<string>(),            (ed, source) => source.getchildkeys(ed, path))        .distinct(stringcomparer.ordinalignoreca)        .lect(key => root.getction(path == null ? key : configurationpath.combine(path, key)));}

通过这段代码再次应验了那句话所有获取配置数据都是面向iconfiguration接口的,数据本质都是来自于iconfigurationprovider读取配置源中的数据。

configurationbuilderproperties

在configurationmanager中还包含了一个properties属性,这个属性本质来源于iconfigurationbuilder。在iconfigurationbuilder中它和iconfigurationsource是平行关系,iconfigurationsource用于在配置源中获取数据,而properties是在内存中获取数据,本质是一个字典

private readonly configurationbuilderproperties _properties = new configurationbuilderproperties(this);idictionary<string, object> iconfigurationbuilder.properties => _properties;

这里咱们就不细说这个具体实现了,我们知道它本质是字典,然后操作都是纯内存的操作即可,来看一下它的定义[点击查看源码]

private class configurationbuilderproperties : idictionary<string, object>{}

基本上许多缓存机制即内存操作都是基于字典做的一部分实现,所以大家对这个实现的方式有一定的认识即可,即使在配置体系的核心操作configurationprovider中读取的配置数据也是存放在字典中的。这个可以去configurationprovider类中自行了解一下[点击查看源码]

protected idictionary<string, string> data { get; t; }protected configurationprovider(){    data = new dictionary<string, string>(stringcomparer.ordinalignoreca);}

总结

通过本文我们了解到了.net6配置体系中的新成员configurationmanager,它是一个新内容但不是一个新技术,因为它是在原有的配置体系中封装了一个新的外观,以简化原来对配置相关的操作。原来对配置的操作需要涉及iconfigurationbuilder和iconfiguration两个抽象操作,而新的configurationmanager只需要一个类,其本质是因为configurationmanage同时实现了iconfigurationbuilder和iconfiguration接口,拥有了他们两个体系的能力。整体来说重写了iconfigurationbuilder的实现为主,而读取操作主要还是借助原来的configurationroot对节点数据的读取操作。

到此这篇关于.net6configurationmanager的实现的文章就介绍到这了,更多相关.net6configurationmanager实现内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

本文发布于:2023-04-04 12:21:10,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/be5c2ff5f685c17b558857fed9cda1e3.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:.NET6 ConfigurationManager的实现及使用方式.doc

本文 PDF 下载地址:.NET6 ConfigurationManager的实现及使用方式.pdf

标签:操作   方法   源码   点击查看
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图