欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core 3.0之深入源码理解Configuration(三)

程序员文章站 2023-01-29 22:17:37
写在前面 上一篇文章讨论了文件型配置的基本内容,本篇内容讨论JSON型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通。看过了上一篇文章的朋友,应该看得出来似曾相识。此图主要表达了文件型配置的实现,当然其他配置,包括自定义配置,都会按照这样的方式去实现。 J... ......

写在前面

文章讨论了文件型配置的基本内容,本篇内容讨论json型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通。看过了文章的朋友,应该看得出来似曾相识。此图主要表达了文件型配置的实现,当然其他配置,包括自定义配置,都会按照这样的方式去实现。

json配置组件的相关内容

该组件有四个类

  • jsonconfigurationextensions
  • jsonconfigurationsource
  • jsonconfigurationfileparser
  • jsonconfigurationprovider

这四个类相互合作,职责明确,共同将json类型的配置加载到内存中,供相应的系统使用。

jsonconfigurationfileparser

该类是一个内部类,拥有一个私有的构造方法,意味着该类无法在其他地方进行实例化,只能在自己内部使用。它只有一个公共方法,并且是静态的,如下所示

   1:  public static idictionary<string, string> parse(stream input) => new jsonconfigurationfileparser().parsestream(input);

该方法通过读取输入数据流,将其转化为字典类型的配置数据,该字典类型是sorteddictionary类型的,并且不区分大小写,此处需要注意。这也呼应了之前所说的.net core configuration对外使用的时候,都是以字典方式去提供给外界使用的。

那么,这个类是如何将数据流转化为json的呢,我们继续阅读源码

   1:  private idictionary<string, string> parsestream(stream input)
   2:  {
   3:      _data.clear();
   4:   
   5:      using (var reader = new streamreader(input))
   6:      using (jsondocument doc = jsondocument.parse(reader.readtoend(), new jsonreaderoptions { commenthandling = jsoncommenthandling.skip }))
   7:      {
   8:          if (doc.rootelement.type != jsonvaluetype.object)
   9:          {
  10:              throw new formatexception(resources.formaterror_unsupportedjsontoken(doc.rootelement.type));
  11:          }
  12:          visitelement(doc.rootelement);
  13:      }
  14:   
  15:      return _data;
  16:  }

通过源码,我们知道,此处使用了jsondocument处理streamreader数据,jsondocument又是什么呢,通过命名空间我们知道,它位于system.text.json中,这是.net core原生的处理json的组件,有兴趣的朋友可以去翻翻msdn或者github查找相关资料。此处不做过多说明。

visitelement方法主要是遍历jsonelement.enumerateobject()方法中的对象集合,此处采用stack<string>实例进行数据安全方面的控制。其中visitvalue是一个在处理json时相当全面的方法,说它全面是因为它考虑到了json值的几乎所有类型:

  • jsonvaluetype.object
  • jsonvaluetype.array
  • jsonvaluetype.number
  • jsonvaluetype.string
  • jsonvaluetype.true
  • jsonvaluetype.false
  • jsonvaluetype.null

当然,该方法,并不会很傻的处理每一种类型,主要是针对object和array类型进行了递归遍历,以便在诸如number、string等的简单类型时跳出递归,并存放到字典中,需要再次强调的是,存放在字典中的值是以string类型存储的。

至此,jsonconfigurationfileparser完成了从文件读取内容并转化为键值对的工作。

jsonconfigurationsource

这个类比较简单,因为继承自fileconfigurationsource,如前文所说,fileconfigurationsource类已经做了初步的实现,只提供了一个build方法交给子类去重写。其返回值是jsonconfigurationprovider实例。

   1:  /// <summary>
   2:  /// represents a json file as an <see cref="iconfigurationsource"/>.
   3:  /// </summary>
   4:  public class jsonconfigurationsource : fileconfigurationsource
   5:  {
   6:      /// <summary>
   7:      /// builds the <see cref="jsonconfigurationprovider"/> for this source.
   8:      /// </summary>
   9:      /// <param name="builder">the <see cref="iconfigurationbuilder"/>.</param>
  10:      /// <returns>a <see cref="jsonconfigurationprovider"/></returns>
  11:      public override iconfigurationprovider build(iconfigurationbuilder builder)
  12:      {
  13:          ensuredefaults(builder);
  14:          return new jsonconfigurationprovider(this);
  15:      }
  16:  }

此处的ensuredefaults()方法,主要是设置fileprovider实例以及指定加载类型的异常处理方式。

jsonconfigurationprovider

这个类也很简单,它继承于fileconfigurationprovider,fileconfigurationprovider本身也已经通用功能进行了抽象实现,先看一下这个类的源码

   1:  /// <summary>
   2:  /// a json file based <see cref="fileconfigurationprovider"/>.
   3:  /// </summary>
   4:  public class jsonconfigurationprovider : fileconfigurationprovider
   5:  {
   6:      /// <summary>
   7:      /// initializes a new instance with the specified source.
   8:      /// </summary>
   9:      /// <param name="source">the source settings.</param>
  10:      public jsonconfigurationprovider(jsonconfigurationsource source) : base(source) { }
  11:   
  12:      /// <summary>
  13:      /// loads the json data from a stream.
  14:      /// </summary>
  15:      /// <param name="stream">the stream to read.</param>
  16:      public override void load(stream stream)
  17:      {
  18:          try {
  19:              data = jsonconfigurationfileparser.parse(stream);
  20:          } catch (jsonreaderexception e)
  21:          {
  22:              throw new formatexception(resources.error_jsonparseerror, e);
  23:          }
  24:      }
  25:  }

其构造函数的传入参数类型是jsonconfigurationsource,这和jsonconfigurationsource.build()方法中的return new jsonconfigurationprovider(this)代码片段相呼应。

jsonconfigurationprovider所重写的方法,调用的是jsonconfigurationfileparser.parse(stream)方法,所以该类显得非常的轻量。

jsonconfigurationextensions

这个方法,大家就更熟悉了,我们平时所使用的addjsonfile()方法,就是在这个扩展类中进行扩展的,其返回值是iconfigurationbuilder类型,其核心方法源码如下所示

   1:  /// <summary>
   2:  /// adds a json configuration source to <paramref name="builder"/>.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/> to add to.</param>
   5:  /// <param name="provider">the <see cref="ifileprovider"/> to use to access the file.</param>
   6:  /// <param name="path">path relative to the base path stored in 
   7:  /// <see cref="iconfigurationbuilder.properties"/> of <paramref name="builder"/>.</param>
   8:  /// <param name="optional">whether the file is optional.</param>
   9:  /// <param name="reloadonchange">whether the configuration should be reloaded if the file changes.</param>
  10:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
  11:  public static iconfigurationbuilder addjsonfile(this iconfigurationbuilder builder, ifileprovider provider, string path, bool optional, bool reloadonchange)
  12:  {
  13:      if (builder == null)
  14:      {
  15:          throw new argumentnullexception(nameof(builder));
  16:      }
  17:      if (string.isnullorempty(path))
  18:      {
  19:          throw new argumentexception(resources.error_invalidfilepath, nameof(path));
  20:      }
  21:   
  22:      return builder.addjsonfile(s =>
  23:      {
  24:          s.fileprovider = provider;
  25:          s.path = path;
  26:          s.optional = optional;
  27:          s.reloadonchange = reloadonchange;
  28:          s.resolvefileprovider();
  29:      });
  30:  }

不过,大家不要看这个方法的代码行数很多,就认为,其他方法都重载与该方法,其实该方法重载自

   1:  /// <summary>
   2:  /// adds a json configuration source to <paramref name="builder"/>.
   3:  /// </summary>
   4:  /// <param name="builder">the <see cref="iconfigurationbuilder"/> to add to.</param>
   5:  /// <param name="configuresource">configures the source.</param>
   6:  /// <returns>the <see cref="iconfigurationbuilder"/>.</returns>
   7:  public static iconfigurationbuilder addjsonfile(this iconfigurationbuilder builder, action<jsonconfigurationsource> configuresource)
   8:      => builder.add(configuresource);

这个方法最终调用的还是iconfigurationbuilder.add()方法

总结

通过介绍以上json configuration组件的四个类,我们知道了,该组件针对json格式的文件的处理方式,不过由于其实文件型配置,其抽象实现已经在文件型配置扩展实现。

从这里,我们可以学习一下,如果有一天我们需要扩展远程配置,比如consul、zk等,我们也可以考虑并采用这种架构的设计方式。另外在json configuration组件中,.net core将专有型功能方法的处理进行了聚合,并聚焦关注点的方式也值得我们学习。

最后jsonconfigurationfileparser中给了我们一种关于stream转换成json的实现,我们完全可以把这个类当成工具类去使用。