[SpringBoot] SpringBoot が yml を解析する全プロセスを詳細に説明する 18 枚の図

数日前、プロジェクトでは、コード内でロジックの一部を実行するかどうかを制御するためにスイッチが必要であるという要件がありました。そのため、ファイル内のプロパティをスイッチとして構成するのは自然であり、その後、変更することができymlますnacos。この値は、目標を達成するためにいつでも yml ファイルに書き込まれます。

switch:
  turnOn: on

プログラム内のコードも非常にシンプルで、一般的なロジックは以下の通りで、取得した switch フィールドが yes であれば判定のコードonが実行され、それ以外の場合は実行されません。if

@Value("${switch.turnOn}")
private String on;

@GetMapping("testn")
public void test(){
    
    
    if ("on".equals(on)){
    
    
        //TODO
    }
}

しかし、実際にコードを実行してみると、興味深いのが、判定のコードはデバッグするまで実行されないこと、そしてここで得られた値が実際には ではないことがわかりonますtrue

onこれを見ると、少し面白いと思いませんか? まず、盲目的な推測ですが、これはyml の解析プロセスで特別な値として扱われるため、さらにいくつかの例をテストし、yml の属性を次のように拡張しました。 :

switch:
  turnOn: on
  turnOff: off
  turnOn2: 'on'
  turnOff2: 'off'

コードを再度実行して、マップされた値を確認します。

yml の引用符なしの合計はsum に変換されon引用符付きの合計は元の値を変更しないことがわかります。offtruefalse

この時点で、なぜこのような現象が起こったのか、少し不思議に思わずにはいられませんでした。そこで、眠気を抑えてソースコードをめくり、SpringBoot が yml 設定ファイルを読み込むプロセスをじっくり見て、ようやく少しやり方が見えてきたので、少しずつ説明していきます。

設定ファイルのロードには SpringBoot の起動に関する関連知識が必要となるため、SpringBoot の起動にあまり詳しくない場合は、初期の Hydra を見てみるのも良いでしょ以下の導入では、構成ファイルのロードと解析に関するいくつかの重要な手順のみを抽出して分析し、その他の無関係な部分は省略します。

ロードリスナー

SpringBoot プログラムを開始し、実行すると、SpringApplication.run()初期化プロセス中に、インターフェイスを実装するSpringApplication11 個のインターセプターが最初にロードされます。ApplicationListener

これら 11 個は自動的にロードされApplicationListener、拡張子でspring.factories定義され、SPI拡張子によってロードされます。

ここにリストされている 10 個spring-bootは にロードされ、残りの 1 個はspring-boot-autoconfigureにロードされます。このうち最も重要なのは、ConfigFileApplicationListener後述する設定ファイルの読み込みに関係することです。

runメソッドを実行する

インスタンス化が完了するとSpringApplication、次にそのメソッドが実行されますrun

ご覧のとおり、前にロードした 11 個のリスナーは、こちら のgetRunListenersメソッドで取得したリスナーにバインドされています。しかし、メソッドが実行されると、タイプに応じてフィルタリングされ、最終的に実際に実行されるリスナーメソッドは4 つだけで、見たいものは何もありませんSpringApplicationRunListenersEventPublishingRunListenerstartingonApplicationEventConfigFileApplicationListener

runメソッドが実行されるprepareEnvironment、あるApplicationEnvironmentPreparedEventタイプのイベントが作成され、ブロードキャストされます。このとき、全リスナーのうち 7人がこのイベントを聞いて、onApplicationEventそれぞれのメソッドを呼び出します。ConfigFileApplicationListeneronApplicationEvent

メソッドを呼び出すプロセスでは、システム自体の 4 つのポストプロセッサとConfigFileApplicationListenerそれ自体、合計 5 つのポストプロセッサがロードされ、それらのpostProcessEnvironmentメソッドが実行されます。他の 4 つは重要ではないのでスキップできます。より重要なステップは、Loaderインスタンスを作成し、そのloadメソッドを呼び出すことです。

設定ファイルをロードする

これは内部クラスLoaderです。オブジェクトのインスタンス化のプロセスを見てください。ConfigFileApplicationListenerLoader

Loaderオブジェクトのインスタンス化のプロセスで、SPI 拡張機能を介して 2 つのプロパティ ファイル ローダーが再度ロードされます。そのうちの 1 つはYamlPropertySourceLoader後続の yml ファイルのロードと解析に密接に関連しており、もう 1 つはファイルのロードPropertiesPropertySourceLoaderを担当します。propertiesインスタンスが作成されると、次にLoaderそのメソッドが呼び出されます。load

このメソッドではload、デフォルトの構成ファイルのストレージ パスがネストされたループを通過し、デフォルトの構成ファイル名と、対応するサフィックスがさまざまな構成ファイル ローダーによって解析され、最終的に yml 構成ファイルが見つかります。次に、loadForFileExtensionメソッドの実行を開始します。

loadForFileExtensionメソッドでは、最初にファイルclasspath:/application.ymlとしてロードされ、その後正式に開始する準備が整い、以前に作成されたオブジェクトのメソッドが呼び出されますResourceYamlPropertySourceLoaderload

パッケージノード

このメソッドではload、構成ファイルの解析とデータのカプセル化の準備を開始します。

loadOriginTrackedYmlLoaderオブジェクトのメソッドはメソッド内で呼び出されますがload、文字通りに理解することもできますが、その目的はオリジナルの追跡 yml のローダーです。中間の一連のメソッド呼び出しは無視できます。最後で最も重要なステップは、OriginTrackingConstructorオブジェクトのインターフェイスを呼び出してgetDatayml を解析し、それをオブジェクトにカプセル化することです。

yml の解析プロセスでは、実際にビルダーを使用してComposerノードが生成され、そのgetNodeメソッドではパーサー イベントを通じてノードが作成されます。一般的に言えば、yml 内の一連のデータをMappingNodeノードにカプセル化します。その内部は実際には1 つNodeTupleの で構成されList構造は同様で、対応する と のペアで構成されますNodeTuple構造は次のとおりです。MapkeyNodevalueNode

さて、先ほどのメソッド呼び出しのフローチャートに戻りますが、これは記事冒頭のymlファイルの実際の内容に従って描かれています、内容が異なると呼び出し処理も変わります、これだけ理解しておけば大丈夫です具体的な分析をしてみましょう。

まず、MappingNodeノードを作成してswitchカプセル化し、次に外層としてkeyNode別のノードを作成しその下に 4 セットのアトリビュートを同時に保存します。そのため、上に 4 つのループがあります。少し混乱しても大丈夫です。下の図を見て、その構造を一目で理解してください。MappingNodeMappingNodevalueNode

上の図では新しいノードが導入されていますScalarNodeが、その使い方は比較的簡単で、単純な String 型の文字列をノードにカプセル化できます。この時点で、yml 内のデータは解析され、事前にカプセル化されています。おそらく、鋭い目を持つ友人は、なぜ上の図が真ん中にあるのか尋ねるでしょう。さらに、別の属性があります。この属性は何のためのものScalarNodeですvaluetag?

その機能を紹介する前に、それがどのように決定されるかについて説明しましょう。ScannerImplこの部分のロジックはさらに複雑です。クラスメソッドのソース コードを参照してくださいfetchMoreTokens。このメソッドは、yml 内の各メソッドや、、、、、、などの特殊な記号を含む先頭の内容に従って、クラス メソッドを解析する方法決定ます。等の状態。特殊文字を含まない文字列を解析する例を取り上げます。重要でない部分を省略した簡単なプロセスは次のとおりです。keyvalue{ ['%?

この図の中間ステップでは、2 つの重要なオブジェクトScalarTokenとオブジェクトが作成されScalarEvent、それぞれにfortrueという属性があり、この属性を説明するplain必要があるかどうかを理解できます。これは、後で取得する重要な属性の 1 つです。Resolver

上の画像はyamlImplicitResolvers実際には事前にキャッシュされた HashMap であり、Charいくつかの種類の文字間のResolverTuple対応関係が事前に保存されています。

属性が解析されると、対応する頭文字onが取り出されます。その中には がありますもちろん、単に取り出すだけではなく、後ほど属性に対して正規表現マッチングを行い、属性の値と一致するかどうかを確認し、チェックが正しかった場合にのみこれを返しますoResolverTupletagtag:yaml.org.2002:boolregexptag

ScalarNodeこの時点で、中間tag属性がどのように取得され、メソッド呼び出しがレイヤーごとに返され、OriginTrackingConstructor親クラスBaseConstructorのメソッドに戻るかを明確に説明しましたgetData次に、メソッドの実行を続行して、constructDocumentyml ドキュメントの解析を完了します。

呼び出しコンストラクター

にはconstructDocument2 つの重要なステップがあります。最初のステップは、現在のノードが使用するコンストラクターのタイプを推論することです。2 番目のステップは、取得したコンストラクターを使用してノード内の値を再割り当てすることです。簡単なプロセスは次のとおりです。ループスルーする部分Nodevalue

コンストラクターの型を推論するプロセスも非常に簡単で、親クラスではBaseConstructorHashMap がキャッシュされ、tagノードの型と対応するコンストラクターの間のマッピング関係が保存されます。getConstructorこのメソッドでは、tag前のノードに保存されている属性を使用して、使用する特定のコンストラクターを取得します。

tagそれが型の場合、コンストラクターとして内部クラスをbool見つけ、そのメソッドを呼び出してオブジェクトをノードの値としてインスタンス化しますSafeConstructConstructYamlBoolconstructScalarNodevalue

このメソッドではconstruct取得した val が前の値on、次の valBOOL_VALUESも事前に初期化された HashMap で、対応するマッピング関係があらかじめ格納されています。キーは以下のキーワード、値は型Booleanですtrueまたはfalse:

この時点で、yml の属性解析プロセスは基本的に完了し、yml の属性が原理onに変換される理由もわかりました。true最後については、Booleantruefalse文字列への変換方法は@Valueアノテーションによって実現されます。

考え

そこで次の疑問は、yml ファイルの解析ではそのような特別な処理が行われるため、propertiesこれを設定ファイルに置き換えるとどうなるかということです。

sw.turnOn=on
sw.turnOff=off

プログラムを実行して結果を確認します。

properties設定ファイルを使えば普通に結果が読み取れることがわかります 解析処理には特別な処理はないようです 解析処理に関しては、興味のある友人が自分でソースコードを読んでみてください

記事を読んで、この記事を書くためにどんどん薄くなった自分の髪を見て、涙が止まりませんでした。いいねをくれるだけで気持ちが落ち着くのかもしれません。

美しい肌は同じ、面白い魂は100万人に1人、この寒い街でお互いを温めましょう、私は阿Qです、次号でお会いしましょう!

おすすめ

転載: blog.csdn.net/Qingai521/article/details/132010415