記事ディレクトリ
数日前、プロジェクトでは、コード内でロジックの一部を実行するかどうかを制御するためにスイッチが必要であるという要件がありました。そのため、ファイル内のプロパティをスイッチとして構成するのは自然であり、その後、変更することができ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
、引用符付きの合計は元の値を変更しないことがわかります。off
true
false
この時点で、なぜこのような現象が起こったのか、少し不思議に思わずにはいられませんでした。そこで、眠気を抑えてソースコードをめくり、SpringBoot が yml 設定ファイルを読み込むプロセスをじっくり見て、ようやく少しやり方が見えてきたので、少しずつ説明していきます。
設定ファイルのロードには SpringBoot の起動に関する関連知識が必要となるため、SpringBoot の起動にあまり詳しくない場合は、初期の Hydra を見てみるのも良いでしょう。以下の導入では、構成ファイルのロードと解析に関するいくつかの重要な手順のみを抽出して分析し、その他の無関係な部分は省略します。
ロードリスナー
SpringBoot プログラムを開始し、実行すると、SpringApplication.run()
初期化プロセス中に、インターフェイスを実装するSpringApplication
11 個のインターセプターが最初にロードされます。ApplicationListener
これら 11 個は自動的にロードされApplicationListener
、拡張子でspring.factories
定義され、SPI
拡張子によってロードされます。
ここにリストされている 10 個spring-boot
は にロードされ、残りの 1 個はspring-boot-autoconfigure
にロードされます。このうち最も重要なのは、ConfigFileApplicationListener
後述する設定ファイルの読み込みに関係することです。
runメソッドを実行する
インスタンス化が完了するとSpringApplication
、次にそのメソッドが実行されますrun
。
ご覧のとおり、前にロードした 11 個のリスナーは、こちら のgetRunListeners
メソッドで取得したリスナーにバインドされています。しかし、メソッドが実行されると、タイプに応じてフィルタリングされ、最終的に実際に実行されるリスナーメソッドは4 つだけで、見たいものは何もありません。SpringApplicationRunListeners
EventPublishingRunListener
starting
onApplicationEvent
ConfigFileApplicationListener
run
メソッドが実行されるとprepareEnvironment
、あるApplicationEnvironmentPreparedEvent
タイプのイベントが作成され、ブロードキャストされます。このとき、全リスナーのうち 7人がこのイベントを聞いて、onApplicationEvent
それぞれのメソッドを呼び出します。ConfigFileApplicationListener
onApplicationEvent
メソッドを呼び出すプロセスでは、システム自体の 4 つのポストプロセッサとConfigFileApplicationListener
それ自体、合計 5 つのポストプロセッサがロードされ、それらのpostProcessEnvironment
メソッドが実行されます。他の 4 つは重要ではないのでスキップできます。より重要なステップは、Loader
インスタンスを作成し、そのload
メソッドを呼び出すことです。
設定ファイルをロードする
これは内部クラスLoader
です。オブジェクトのインスタンス化のプロセスを見てください。ConfigFileApplicationListener
Loader
Loader
オブジェクトのインスタンス化のプロセスで、SPI 拡張機能を介して 2 つのプロパティ ファイル ローダーが再度ロードされます。そのうちの 1 つはYamlPropertySourceLoader
後続の yml ファイルのロードと解析に密接に関連しており、もう 1 つはファイルのロードPropertiesPropertySourceLoader
を担当します。properties
インスタンスが作成されると、次にLoader
そのメソッドが呼び出されます。load
このメソッドではload
、デフォルトの構成ファイルのストレージ パスがネストされたループを通過し、デフォルトの構成ファイル名と、対応するサフィックスがさまざまな構成ファイル ローダーによって解析され、最終的に yml 構成ファイルが見つかります。次に、loadForFileExtension
メソッドの実行を開始します。
loadForFileExtension
メソッドでは、最初にファイルclasspath:/application.yml
としてロードされ、その後正式に開始する準備が整い、以前に作成されたオブジェクトのメソッドが呼び出されます。Resource
YamlPropertySourceLoader
load
パッケージノード
このメソッドではload
、構成ファイルの解析とデータのカプセル化の準備を開始します。
load
OriginTrackedYmlLoader
オブジェクトのメソッドはメソッド内で呼び出されますがload
、文字通りに理解することもできますが、その目的はオリジナルの追跡 yml のローダーです。中間の一連のメソッド呼び出しは無視できます。最後で最も重要なステップは、OriginTrackingConstructor
オブジェクトのインターフェイスを呼び出してgetData
yml を解析し、それをオブジェクトにカプセル化することです。
yml の解析プロセスでは、実際にビルダーを使用してComposer
ノードが生成され、そのgetNode
メソッドではパーサー イベントを通じてノードが作成されます。一般的に言えば、yml 内の一連のデータをMappingNode
ノードにカプセル化します。その内部は実際には1 つNodeTuple
の で構成されList
、構造は同様で、対応する と のペアで構成されますNodeTuple
。構造は次のとおりです。Map
keyNode
valueNode
さて、先ほどのメソッド呼び出しのフローチャートに戻りますが、これは記事冒頭のymlファイルの実際の内容に従って描かれています、内容が異なると呼び出し処理も変わります、これだけ理解しておけば大丈夫です具体的な分析をしてみましょう。
まず、MappingNode
ノードを作成してswitch
カプセル化し、次に外層としてkeyNode
別のノードを作成し、その下に 4 セットのアトリビュートを同時に保存します。そのため、上に 4 つのループがあります。少し混乱しても大丈夫です。下の図を見て、その構造を一目で理解してください。MappingNode
MappingNode
valueNode
上の図では新しいノードが導入されていますScalarNode
が、その使い方は比較的簡単で、単純な String 型の文字列をノードにカプセル化できます。この時点で、yml 内のデータは解析され、事前にカプセル化されています。おそらく、鋭い目を持つ友人は、なぜ上の図が真ん中にあるのか尋ねるでしょう。さらに、別の属性があります。この属性は何のためのものScalarNode
ですvalue
かtag
?
その機能を紹介する前に、それがどのように決定されるかについて説明しましょう。ScannerImpl
この部分のロジックはさらに複雑です。クラスメソッドのソース コードを参照してくださいfetchMoreTokens
。このメソッドは、yml 内の各メソッドや、、、、、、などの特殊な記号を含む先頭の内容に従って、クラス メソッドを解析する方法を決定します。等の状態。特殊文字を含まない文字列を解析する例を取り上げます。重要でない部分を省略した簡単なプロセスは次のとおりです。key
value
{
[
'
%
?
この図の中間ステップでは、2 つの重要なオブジェクトScalarToken
とオブジェクトが作成されScalarEvent
、それぞれにfortrue
という属性があり、この属性を説明するplain
必要があるかどうかを理解できます。これは、後で取得する重要な属性の 1 つです。Resolver
上の画像はyamlImplicitResolvers
実際には事前にキャッシュされた HashMap であり、Char
いくつかの種類の文字間のResolverTuple
対応関係が事前に保存されています。
属性が解析されると、対応する頭文字on
が取り出されます。その中には があります。もちろん、単に取り出すだけではなく、後ほど属性に対して正規表現マッチングを行い、属性の値と一致するかどうかを確認し、チェックが正しかった場合にのみこれを返します。o
ResolverTuple
tag
tag:yaml.org.2002:bool
regexp
tag
ScalarNode
この時点で、中間tag
属性がどのように取得され、メソッド呼び出しがレイヤーごとに返され、OriginTrackingConstructor
親クラスBaseConstructor
のメソッドに戻るかを明確に説明しましたgetData
。次に、メソッドの実行を続行して、constructDocument
yml ドキュメントの解析を完了します。
呼び出しコンストラクター
にはconstructDocument
2 つの重要なステップがあります。最初のステップは、現在のノードが使用するコンストラクターのタイプを推論することです。2 番目のステップは、取得したコンストラクターを使用してノード内の値を再割り当てすることです。簡単なプロセスは次のとおりです。ループスルーする部分Node
のvalue
コンストラクターの型を推論するプロセスも非常に簡単で、親クラスではBaseConstructor
HashMap がキャッシュされ、tag
ノードの型と対応するコンストラクターの間のマッピング関係が保存されます。getConstructor
このメソッドでは、tag
前のノードに保存されている属性を使用して、使用する特定のコンストラクターを取得します。
tag
それが型の場合、コンストラクターとして内部クラスをbool
見つけ、そのメソッドを呼び出してオブジェクトをノードの値としてインスタンス化します。SafeConstruct
ConstructYamlBool
construct
ScalarNode
value
このメソッドではconstruct
取得した val が前の値on
、次の valBOOL_VALUES
も事前に初期化された HashMap で、対応するマッピング関係があらかじめ格納されています。キーは以下のキーワード、値は型Boolean
ですtrue
またはfalse
:
この時点で、yml の属性解析プロセスは基本的に完了し、yml の属性が原理on
に変換される理由もわかりました。true
最後については、Boolean
型true
やfalse
文字列への変換方法は@Value
アノテーションによって実現されます。
考え
そこで次の疑問は、yml ファイルの解析ではそのような特別な処理が行われるため、properties
これを設定ファイルに置き換えるとどうなるかということです。
sw.turnOn=on
sw.turnOff=off
プログラムを実行して結果を確認します。
properties
設定ファイルを使えば普通に結果が読み取れることがわかります 解析処理には特別な処理はないようです 解析処理に関しては、興味のある友人が自分でソースコードを読んでみてください。
記事を読んで、この記事を書くためにどんどん薄くなった自分の髪を見て、涙が止まりませんでした。いいねをくれるだけで気持ちが落ち着くのかもしれません。
美しい肌は同じ、面白い魂は100万人に1人、この寒い街でお互いを温めましょう、私は阿Qです、次号でお会いしましょう!