.NET Core 3.0之深入原始碼理解Configuration(三)

艾心❤發表於2019-05-19

寫在前面

上一篇文章討論了檔案型配置的基本內容,本篇內容討論JSON型配置的實現方式,理解了這一種配置型別的實現方式,那麼其他型別的配置實現方式基本可以觸類旁通。看過了上一篇文章的朋友,應該看得出來似曾相識。此圖主要表達了檔案型配置的實現,當然其他配置,包括自定義配置,都會按照這樣的方式去實現。

繪圖3

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的實現,我們完全可以把這個類當成工具類去使用。

相關文章