[Spring Boot] Spring — リスナーの読み込み

序文

数日前、プロジェクトに要件があり、コード内のロジックを実行するかどうかを制御するスイッチが必要でした。そのため、もちろん yml ファイル内の属性をスイッチとして設定し、nacos を使用して、目的を達成するためにいつでもこの値を変更できます。これは yml ファイルに書き込まれます。

switch:
  turnOn: 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 内の引用符のない on と off は true と false に変換されますが、引用符は元の値を変更しません。

この時点で、私は少し不思議に思わずにはいられませんが、なぜこのようなことが起こるのでしょうか?ということで、眠いのを我慢してソースコードをめくり、SpringBootでyml設定ファイルを読み込む処理に四苦八苦していたところ、ようやくヒントが見えてきたので、以下で詳しく解説していきます!

設定ファイルの読み込みには Spring Boot の起動に関するある程度の知識が必要となるため、Spring Boot の起動にあまり慣れていない場合は、まず古代の Hydra によって書かれた Spring Boot のゼロ設定起動原則を読んでウォームアップしてください。以下の導入では、構成ファイルのロードと解析のためのいくつかの重要な手順のみが分析のために抽出され、その他の無関係な部分は省略されます。

ロードリスナー

SpringBootプログラムを起動してSpringApplication.run()を実行すると、まずSpringApplicationの初期化処理中に、ApplicationListenerインターフェースを実装した11個のインターセプタがロードされる。
画像の説明を追加してください

これらの 11 個の自動的にロードされる ApplicationListener は spring.factories で定義され、SPI 拡張機能を通じてロードされます。

画像の説明を追加してください

ここにリストされている 10 個は spring-boot にロードされ、残りの 1 つは spring-boot-autoconfigure にロードされます。最も重要なものは ConfigFileApplicationListener で、これは後で説明する構成ファイルのロードに関連します。

runメソッドを実行する

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

画像の説明を追加してください

ご覧のとおり、getRunListeners メソッドで取得した SpringApplicationRunListeners のうち、EventPublishingRunListener は、先ほどロードした 11 個のリスナーにバインドされています。しかし、開始メソッドの実行時に型に基づいてフィルタリングが行われ、最終的には 4 つのリスナーの onApplicationEvent メソッドのみが実際に実行され、期待していた ConfigFileApplicationListener は存在しませんでした。

画像の説明を追加してください

run メソッドが prepareEnvironment に対して実行されると、ApplicationEnvironmentPreparedEvent タイプのイベントが作成され、ブロードキャストされます。この時点では、すべてのリスナーのうち 7 つがこのイベントをリッスンし、それぞれの onApplicationEvent メソッドが呼び出されます。その中には、私たちが考えている ConfigFileApplicationListener があります。次に、その onApplicationEvent で何が行われるかを見てみましょう。方法。

画像の説明を追加してください

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

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

    这里的Loader是ConfigFileApplicationListener的一个内部类,看一下Loader对象实例化的过程:

画像の説明を追加してください

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

画像の説明を追加してください

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

画像の説明を追加してください

loadForFileExtension メソッドでは、最初に classpath:/application.yml がリソース ファイルとして読み込まれ、その後、事前に作成された YamlPropertySourceLoader オブジェクトの load メソッドを呼び出すことで準備が正式に開始されます。

ノードのカプセル化

    在load方法中,开始准备进行配置文件的解析与数据封装:

画像の説明を追加してください

このloadメソッドはOriginTrackedYmlLoaderオブジェクトのloadメソッドを呼び出しており、文字通りの意味から、元のトラッキングymlのローダーが目的であることも分かります。中間の一連のメソッド呼び出しは無視してかまいません。最後の最も重要なステップを直接見てみましょう。OriginTrackingConstructor オブジェクトの getData インターフェイスを呼び出して、yml を解析し、それをオブジェクトにカプセル化します。

画像の説明を追加してください

yml の解析プロセスでは、実際には Composer ビルダーを使用してノードが生成され、その getNode メソッドで、パーサー イベントを通じてノードが作成されます。一般的には、yml 内の一連のデータを MappingNode ノードにカプセル化します。その内部は実際には NodeTuple で構成される List です。NodeTuple の構造は Map に似ており、対応する keyNode と valueNode のペアで構成されます。構造は次のとおりです。 :
画像の説明を追加してください

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

まず、MappingNode ノードを作成し、スイッチを keyNode にカプセル化してから、外側の MappingNode の valueNode として MappingNode を作成し、その下に 4 セットの属性を格納します。これが、上に 4 つのループがある理由です。少し混乱していても、下の図を見ていただければ、一目で構造が理解できると思います。
画像の説明を追加してください

上の図では、新しい ScalarNode ノードが導入されていますが、その目的は比較的単純で、単純な String 型の文字列をノードにカプセル化できます。この時点で、yml 内のデータは解析され、予備的なカプセル化が完了しました。鋭い目を持つ友人は、なぜ上の図の ScalarNode に value に加えて tag 属性があるのか​​疑問に思うかもしれません。この属性は何に使用されますか?

その機能を紹介する前に、まずその決定方法について説明しましょう。この部分のロジックは比較的複雑です。ScannerImpl クラスの fetchMoreTokens メソッドのソース コードを確認できます。このメソッドは、yml 内の各キーまたは値 ({、[、 '、%、?、およびその他の特殊記号。特殊文字を含まない文字列を解析する例として、いくつかの重要でない部分を省略した簡単なプロセスは次のとおりです。

画像の説明を追加してください

この図の中間ステップでは、ScalarToken と ScalarEvent という 2 つのさらに重要なオブジェクトが作成されており、どちらも true というプレーン属性を持っています。この属性が説明を必要とするかどうかが、リゾルバーを取得するための重要な属性の 1 つであることが理解できます。後で。

上の図の yamlImplicitResolvers は、実際には事前にキャッシュされた HashMap であり、一部の Char 型文字と ResolverTuple の対応関係が事前に保存されています。

画像の説明を追加してください

on 属性を解析すると、頭文字 o に対応する ResolverTuple が取り出され、タグは tag:yaml.org.2002:bool となります。もちろん、ただ取り出すだけではなく、属性を正​​規表現と照合して、正規表現の値と一致するかどうかを確認し、チェックが正しかった場合にのみタグを返します。

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

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

    在constructDocument中,有两步比较重要,第一步是推断当前节点应该使用哪种类型的构造器,第二步是使用获得的构造器来重新对Node节点中的value进行赋值,简易流程如下,省去了循环遍历的部分:

画像の説明を追加してください

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

タグが bool 型の場合、SafeConstruct の内部クラス ConstructYamlBool がコンストラクターとして検出され、その構築メソッドが呼び出されて、ScalarNode ノードの値としてオブジェクトをインスタンス化します。
画像の説明を追加してください

コンストラクトメソッドでは取得したvalが前回のものです 以下のBOOL_VALUESも事前に初期化されたHashMapです 対応するマッピング関係は事前にいくつか格納されています キーは以下のキーワード、値はIsですブール型 true または false:
画像の説明を追加してください

この時点で、yml の属性解析プロセスは基本的に完了し、yml の on が true に変換される理由の原理も理解できました。最後のステップとして、Boolean 型の true または false を文字列に変換する方法は @Value アノテーションによって実装されます。

考える

    那么,下一个问题来了,既然yml文件解析中会做这样的特殊处理,那么如果换成properties配置文件怎么样呢?
sw.turnOn=on
sw.turnOff=off

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

画像の説明を追加してください

プロパティ設定ファイルからは普通に結果が読み取れることが分かります 解析処理では特別な処理は行われていないようです 解析処理に関しては、興味のある友人が自分でソースコードを読むことができます。

おすすめ

転載: blog.csdn.net/2202_75623950/article/details/132942968