1. 序章

Spring Bootアプリケーションには、複雑なコンポーネントグラフ、起動フェーズ、およびリソース初期化ステップが含まれる場合があります。

この記事では、アクチュエータエンドポイントを介してこのスタートアップ情報を追跡および監視する方法を見ていきます。

2. アプリケーションの起動追跡

アプリケーションの起動中のさまざまな手順を追跡すると、アプリケーションの起動のさまざまなフェーズで費やされた時間を理解するのに役立つ有用な情報が得られます。 このようなインストルメンテーションは、コンテキストライフサイクルとアプリケーション起動シーケンスの理解を向上させることもできます。

Spring Framework は、アプリケーションの起動とグラフの初期化を記録する機能を提供します。 さらに、Spring Boot Actuatorは、HTTPまたはJMXを介していくつかの実稼働グレードの監視および管理機能を提供します。

Spring Boot 2.4 以降、アプリケーションの起動追跡メトリックが/actuator/startupエンドポイントから利用できるようになりました。

3. 設定

Spring Bootアクチュエータを有効にするには、 spring-boot-starter-actuator依存関係をPOMに追加しましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.5.4</version>
</dependency>

spring -boot-starter-web 依存関係も追加します。これは、HTTP経由でエンドポイントにアクセスするために必要なためです。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.4</version>
</dependency>

さらに、 application.properties ファイルで構成プロパティを設定することにより、必要なエンドポイントをHTTP経由で公開します。

management.endpoints.web.exposure.include=startup

最後に、curljqを使用して、アクチュエーターのHTTPエンドポイントをクエリし、JSON応答を解析します。

4. アクチュエータエンドポイント

起動イベントをキャプチャするには、@ApplicationStartupインターフェイスの実装を使用してアプリケーションを構成する必要があります。 デフォルトでは、アプリケーションのライフサイクルを管理するためのApplicationContextはno-op実装を使用します。 これは明らかに、最小限のオーバーヘッドのために、起動時の計測と追跡を実行しません。

したがって、他のアクチュエータエンドポイントとは異なり、追加のセットアップが必要です。

4.1. BufferingApplicationStartupを使用する

アプリケーションのスタートアップ構成をBufferingApplicationStartupのインスタンスに設定する必要があります。これは、Spring Bootによって提供されるApplicationStartupインターフェイスのメモリ内実装です。 は、Springの起動プロセス中にイベントをキャプチャし、それらを内部バッファに格納します。

このアプリケーションの実装を使用して、単純なアプリケーションを作成することから始めましょう。

@SpringBootApplication
public class StartupTrackingApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(StartupTrackingApplication.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }
}

ここでは、内部バッファに2048の容量も指定しました。 バッファがイベントのこの容量に達すると、それ以上のデータは記録されません。 したがって、適切な値を使用して、アプリケーションの複雑さと起動時に実行されるさまざまなステップに基づいてイベントを保存できるようにすることが重要です。

重要なことに、アクチュエータエンドポイントは、この実装が構成された場合にのみ使用可能になります

4.2. スタートアップエンドポイント

これで、アプリケーションを起動して、startupアクチュエータエンドポイントをクエリできます。

curl を使用してこのPOSTエンドポイントを呼び出し、jqを使用してJSON出力をフォーマットしてみましょう。

> curl 'http://localhost:8080/actuator/startup' -X POST | jq
{
  "springBootVersion": "2.5.4",
  "timeline": {
    "startTime": "2021-10-17T21:08:00.931660Z",
    "events": [
      {
        "endTime": "2021-10-17T21:08:00.989076Z",
        "duration": "PT0.038859S",
        "startTime": "2021-10-17T21:08:00.950217Z",
        "startupStep": {
          "name": "spring.boot.application.starting",
          "id": 0,
          "tags": [
            {
              "key": "mainApplicationClass",
              "value": "com.baeldung.startup.StartupTrackingApplication"
            }
          ],
          "parentId": null
        }
      },
      {
        "endTime": "2021-10-17T21:08:01.454239Z",
        "duration": "PT0.344867S",
        "startTime": "2021-10-17T21:08:01.109372Z",
        "startupStep": {
          "name": "spring.boot.application.environment-prepared",
          "id": 1,
          "tags": [],
          "parentId": null
        }
      },
      ... other steps not shown
      {
        "endTime": "2021-10-17T21:08:12.199369Z",
        "duration": "PT0.00055S",
        "startTime": "2021-10-17T21:08:12.198819Z",
        "startupStep": {
          "name": "spring.boot.application.running",
          "id": 358,
          "tags": [],
          "parentId": null
        }
      }
    ]
  }
}

ご覧のとおり、詳細なJSON応答には、インストルメント化された起動イベントのリストが含まれています。 ステップ名、開始時間、終了時間、ステップタイミングの詳細など、各ステップに関するさまざまな詳細が含まれています。 応答構造の詳細については、 Spring Boot Actuator WebAPIのドキュメントを参照してください。

さらに、コアコンテナで定義されているステップの完全なリストと各ステップの詳細については、Springリファレンスドキュメントを参照してください。

ここで注意すべき重要な詳細は、エンドポイントの後続の呼び出しでは詳細なJSON応答が提供されないことです。 これは、スタートアップエンドポイントの呼び出しによって内部バッファがクリアされるためです。 したがって、同じエンドポイントを呼び出して完全な応答を再度受信するには、アプリケーションを再起動する必要があります。

必要に応じて、さらに分析するためにペイロードを保存する必要があります

4.3. スタートアップイベントのフィルタリング

これまで見てきたように、バッファリングの実装には、イベントをメモリに格納するための固定容量があります。 したがって、バッファに多数のイベントを格納することは望ましくない場合があります。

インストルメント化されたイベントをフィルタリングし、関心のある可能性のあるイベントのみを保存できます。

BufferingApplicationStartup startup = new BufferingApplicationStartup(2048);
startup.addFilter(startupStep -> startupStep.getName().matches("spring.beans.instantiate");

ここでは、 addFilter メソッドを使用して、指定された名前のステップのみをインストルメントしました。

4.4. カスタム計装

BufferingApplicationStartup を拡張して、特定のインストルメンテーションのニーズを満たすカスタムのスタートアップ追跡動作を提供することもできます。

このインストルメンテーションは、本番環境ではなくテスト環境で特に価値があるため、システムプロパティを使用して、no-opとバッファリングまたはカスタム実装を切り替えるのは簡単な演習です。

5. 起動時間の分析

実際の例として、初期化に比較的長い時間がかかる可能性のある起動中のbeanインスタンス化を特定してみましょう。 たとえば、これは、キャッシュの読み込み、データベース接続のプーリング、またはアプリケーションの起動中の他の高価な初期化である可能性があります。

以前と同じようにエンドポイントを呼び出すことができますが、今回はjqを使用して出力を処理します。

応答は非常に冗長なので、 spring .beans.instantiate という名前に一致するステップでフィルター処理し、期間で並べ替えてみましょう。

> curl 'http://localhost:8080/actuator/startup' -X POST \
| jq '[.timeline.events
 | sort_by(.duration) | reverse[]
 | select(.startupStep.name | match("spring.beans.instantiate"))
 | {beanName: .startupStep.tags[0].value, duration: .duration}]'

上記の式は、応答JSONを処理してタイミング情報を抽出します。

  • timeline.events配列を降順で並べ替えます。
  • ソートされた配列から、名前spring.beans.instantiateに一致するすべてのステップを選択します。
  • 一致する各ステップからbeanNamedurationを使用して新しいJSONオブジェクトを作成します。

その結果、出力には、アプリケーションの起動中にインスタンス化されたさまざまなBeanの簡潔で順序付けられ、フィルター処理されたビューが表示されます。

[
  {
    "beanName": "resourceInitializer",
    "duration": "PT6.003171S"
  },
  {
    "beanName": "tomcatServletWebServerFactory",
    "duration": "PT0.143958S"
  },
  {
    "beanName": "requestMappingHandlerAdapter",
    "duration": "PT0.14302S"
  },
  ...
]

ここでは、resourceInitializerBeanの起動に約6秒かかっていることがわかります。 これは、アプリケーションの起動時間全体にかなりの期間貢献していると見なすことができます。 このアプローチを使用すると、この問題を効果的に特定し、さらなる調査と可能な解決策に焦点を当てることができます

ApplicationStartupは、アプリケーションの起動時でのみ使用することを目的としていることに注意してください。 つまり、は、アプリケーションインストルメンテーションのJavaプロファイラーおよびメトリック収集フレームワークに置き換わるものではありません。

6. 結論

この記事では、SpringBootアプリケーションで詳細なスタートアップメトリックを取得して分析する方法について説明しました。

最初に、SpringBootActuatorエンドポイントを有効にして構成する方法を確認しました。 次に、このエンドポイントから得られた有用な情報を調べました。

最後に、アプリケーションの起動時のさまざまな手順をよりよく理解するために、この情報を分析する例を確認しました。

いつものように、この記事のコードはGitHubから入手できます。