Espressoによる強化されたホットスワップ機能

Espressoを使用すると、実行中のアプリケーションを再起動する必要なく、開発中にコードを自然に進化させることができる強化されたホットスワップ機能の恩恵を受けることができます。強化されたホットスワップのメリットを得るために、デバッグモードでアプリを起動し、標準のIDEデバッガーをアタッチする以外に、特別な構成は必要ありません。

Espressoでのデバッグ #

お気に入りのIDEデバッガーを使用して、Espressoランタイムで実行されているJavaアプリケーションをデバッグできます。たとえば、IntelliJ IDEAからデバッガーセッションを開始するには、実行構成に基づいています。同じ環境でJavaアプリケーションにデバッガーをアタッチするには、メインメニューの実行デバッグ...構成の編集に移動し、環境を展開し、JRE値とVMオプション値を確認します。プロジェクトのJREとしてGraalVMが表示され、VMオプションには-truffle -XX:+IgnoreUnrecognizedVMOptionsが含まれている必要があります。Intellijはまだサポートされていない-javaagent引数を自動的に追加するため、-XX:+IgnoreUnrecognizedVMOptionsを指定する必要があります。「デバッグ」を押します。

これにより、アプリケーションが実行され、バックグラウンドでデバッガーセッションが開始されます。

デバッグセッション中のホットスワップの使用 #

デバッガーセッションが実行されると、セッションを再起動することなく、広範なコード変更(ホットスワップ)を適用できるようになります。ご自身のアプリケーションで試してみるか、次の手順に従って試してみてください。

  1. 新しいJavaアプリケーションを作成します。
  2. 開始点として、次のmainメソッドを使用します。
     public class HotSwapDemo {
    
         private static final int ITERATIONS = 100;
    
         public static void main(String[] args) {
             HotSwapDemo demo = new HotSwapDemo();
             System.out.println("Starting HotSwap demo with Espresso: 'java.vm.name' = " + System.getProperty("java.vm.name"));
             // run something in a loop
             for (int i = 1; i <= ITERATIONS; i++) {
                 demo.runDemo(i);
             }
             System.out.println("Completed HotSwap demo with Espresso");
         }
    
         public void runDemo(int iteration) {
             int random = new Random().nextInt(iteration);
             System.out.printf("\titeration %d ran with result: %d\n", iteration, random);
         }
     }
    
  3. java.vm.nameプロパティにEspressoで実行されていると表示されていることを確認します。
  4. runDemo()の最初の行にラインブレークポイントを設定します。
  5. Espressoで実行するように実行構成を設定し、「デバッグ」を押します。以下が表示されます。

    HotSwap Debugging Session: Debug Output

  6. ブレークポイントで一時停止している間に、runDemo()の本文からメソッドを抽出します。

    HotSwap Debugging Session: Extract Method

  7. 実行 -> デバッグアクション -> 変更されたクラスのリロードに移動して、変更をリロードします。

    HotSwap Debugging Session: Reload Changed Classes

  8. デバッグ -> フレームビューで<obsolete>:-1現在のフレームを確認することで、変更が適用されたことを確認します。

    HotSwap Debugging Session: Frames View

  9. 新しく抽出されたメソッドの最初の行にブレークポイントを設定し、「プログラムの再開」を押します。ブレークポイントがヒットします。

    HotSwap Debugging Session: Set a Breakpoint and Resume Program

  10. printRandom()のアクセス修飾子をprivateからpublic staticに変更してみます。変更をリロードします。「プログラムの再開」を押して、変更が適用されたことを確認します。

    HotSwap Debugging Session: Change Access Modifiers

Espressoデモによる強化されたホットスワップ機能のビデオをご覧ください。

サポートされている変更 #

Espressoの強化されたホットスワップは、ほぼ機能が完了しています。次の変更がサポートされています。

  • メソッドの追加と削除
  • コンストラクターの追加と削除
  • インターフェースからのメソッドの追加と削除
  • メソッドのアクセス修飾子の変更
  • コンストラクターのアクセス修飾子の変更
  • フィールドの追加と削除
  • フィールドタイプの変更
  • 階層内でのフィールドの移動と状態の保持(以下の注意を参照)
  • クラスのアクセス修飾子の変更(例:abstractおよびfinal修飾子)
  • ラムダの変更
  • 新しい匿名内部クラスの追加
  • 匿名内部クラスの削除
  • スーパークラスの変更
  • 実装されたインターフェースの変更

注意: インスタンスフィールドがクラス階層内で移動された場合、可能な限り状態は保持されます。例としては、「フィールドの引き上げ」のリファクタリングがあり、元のサブクラスの既存のすべてのインスタンスは、スーパークラスのフィールドから以前に保存された値を読み取ることができます。一方、変更前にフィールドが存在しなかった、関連のないサブクラスインスタンスの場合、新しいフィールドの値は言語のデフォルト値(オブジェクト型フィールドの場合はnull、intの場合は0など)になります。

次の制限が残っています。

  • 列挙型の変更

ホットスワッププラグインAPI #

Espressoを使用すると、実行中のアプリケーションを再起動する必要なく、開発中にコードを自然に進化させることができる強化されたホットスワップ機能の恩恵を受けることができます。コードの再ロード(ホットスワップ)は強力なツールですが、アノテーションの変更、実装されたサービスやBeanなどのフレームワーク固有の変更など、あらゆる種類の変更を反映するには十分ではありません。これらの場合、変更が実行中のインスタンスに完全に反映される前に、構成やコンテキストを再ロードするためにコードを実行する必要があることがよくあります。ここで、EspressoホットスワッププラグインAPIが役に立ちます。

ホットスワッププラグインAPIは、IDEでソースコードが編集された際に応答して変更を反映するための適切なフックを設定することにより、フレームワーク開発者を対象としています。主な設計原則は、指定されたホットスワップイベントでトリガーされるさまざまなホットスワップリスナーを登録できることです。例としては、静的イニシャライザーの再実行、汎用のポストホットスワップコールバック、特定のサービスプロバイダーの実装が変更されたときのフックなどがあります。

注意: ホットスワッププラグインAPIは開発中であり、コミュニティからの要望に応じて、より詳細なホットスワップリスナーの登録が追加される可能性があります。コミュニティサポートチャネルを通じて、APIの形成に役立つ機能拡張リクエストをお送りください。

Micronautでより強力な再ロードサポートを可能にする、実行中の例を調べて、ホットスワッププラグインAPIを確認してください。

Micronautホットスワッププラグイン #

Micronautホットスワッププラグインのサンプル実装は、Micronaut-coreのフォークとしてホストされています。以下の手順はmacOS Xの設定に基づいており、Windowsの場合はわずかな変更のみが必要です。開始するには

  1. リポジトリをクローンします。
      git clone git@github.com:javeleon/micronaut-core.git
    
  2. ローカルMavenリポジトリにビルドして公開します。
      cd micronaut-core
      ./gradlew publishMavenPublicationToMavenLocal
    

これで、ホットスワップ対応のMicronautができました。強化されたMicronautバージョンを使用するサンプルアプリケーションをセットアップする前に、プラグインが内部で何を行っているかを見てみましょう。

興味深いクラスはMicronautHotSwapPluginであり、アプリケーションソースコードに特定の変更が行われた場合に再ロードできるアプリケーションコンテキストを保持しています。クラスは次のようになります。

final class MicronautHotSwapPlugin implements HotSwapPlugin {

    private final ApplicationContext context;
    private boolean needsBeenRefresh = false;

    MicronautHotSwapPlugin(ApplicationContext context) {
        this.context = context;
        // register class re-init for classes that provide annotation metadata
        EspressoHotSwap.registerClassInitHotSwap(
                AnnotationMetadataProvider.class,
                true,
                () -> needsBeenRefresh = true);
        // register ServiceLoader listener for declared bean definitions
        EspressoHotSwap.registerMetaInfServicesListener(
                BeanDefinitionReference.class,
                context.getClassLoader(),
                () -> reloadContext());
        EspressoHotSwap.registerMetaInfServicesListener(
                BeanIntrospectionReference.class,
                context.getClassLoader(),
                () -> reloadContext());
    }

    @Override
    public String getName() {
        return "Micronaut HotSwap Plugin";
    }

    @Override
    public void postHotSwap(Class<?>[] changedClasses) {
        if (needsBeenRefresh) {
            reloadContext();
        }
        needsBeenRefresh = false;
    }

    private void reloadContext() {
        if (Micronaut.LOG.isInfoEnabled()) {
            Micronaut.LOG.info("Reloading app context");
        }
        context.stop();
        context.flushBeanCaches();
        context.start();

        // fetch new embedded application bean which will re-wire beans
        Optional<EmbeddedApplication> bean = context.findBean(EmbeddedApplication.class);
        // now restart the embedded app/server
        bean.ifPresent(ApplicationContextLifeCycle::start);
    }
}

ホットスワップAPIに関するロジックは、このクラスのコンストラクターにあります。Micronautはコンパイル時のアノテーション処理を中心に構築されており、アノテーションメタデータが収集され、生成されたクラスの静的フィールドに格納されます。開発者がMicronautアノテーション付きクラスに変更を加えるたびに、対応するメタデータクラスが再生成されます。標準のホットスワップは静的イニシャライザーを再実行しない(また、そうすべきではありません)ため、ホットスワッププラグインを使用すると、メタデータを提供するすべてのクラス(Micronaut生成クラス)に対して静的イニシャライザーが再実行されます。したがって、このAPIメソッドEspressoHotSwap.registerClassInitHotSwapが使用されます。

public static boolean registerClassInitHotSwap(Class<?> klass, boolean onChange, HotSwapAction action)

これにより、特定のクラスとそのサブクラスに対するクラス変更のリスナーが登録されます。onChange変数は、コードが変更された場合にのみ静的イニシャライザーを再実行する必要があるかどうかを指示します。actionパラメーターは、静的イニシャライザーが再実行されるたびに特定のアクションをトリガーするためのフックです。ここでは、静的イニシャライザーが再実行されるたびにneedsBeenRefreshフィールドをtrueに設定する関数を渡します。ホットスワップアクションが完了すると、プラグインはpostHotSwap呼び出しを受信し、trueのneedsBeenRefreshに応じて、reloadContextメソッドでMicronaut固有のコードを実行してアプリケーションコンテキストを再ロードします。

新しいクラスの検出と挿入 #

ホットスワップは、実行中のアプリケーションでクラスをホットスワップできるように設計されています。ただし、開発者がまったく新しいクラス(たとえば、Micronautの新しい@Controllerクラス)を導入した場合、ホットスワップは魔法のように新しいクラスを挿入しません。そうするには、少なくとも内部クラスローディングロジックに関する知識が必要になるためです。

フレームワークによってクラスが検出される標準的な方法は、ServiceLoaderメカニズムを使用することです。ホットスワップAPIには、メソッドEspressoHotSwap.registerMetaInfServicesListenerによってサービス実装変更リスナーを登録するための組み込みサポートがあります。

public static boolean registerMetaInfServicesListener(Class<?> serviceType, ClassLoader loader, HotSwapAction action)

現在のサポートは、META-INF/servicesでのクラスパスベースのサービスデプロイメントのサービス実装変更をリッスンすることに限定されています。登録されたクラスタイプのサービス実装のセットに変更があるたびに、actionがトリガーされます。Micronautホットスワッププラグインでは、reloadContextが実行され、変更が自動的に選択されます。

注意: サービス実装の変更によるホットスワップアクションは、ホットスワップとは無関係にトリガーされます。開発者は、IDEからホットスワップを実行して、実行中のアプリケーションで新しい機能を確認する必要はありません。

Micronautの次世代ホットスワップ #

これで、Micronautホットスワッププラグインの仕組みがわかったので、実際のアプリケーションでこの機能を使用してみましょう。これが、チュートリアル「最初のMicronaut Graalアプリケーションの作成」から作成されたサンプルアプリケーションです。例のソースは、こちらから既製のGradleプロジェクトとしてダウンロードできます。ダウンロード、解凍し、IDEでプロジェクトを開きます。

続行する前に、Espressoがインストールされ、GraalVMがプロジェクトSDKとして設定されていることを確認してください。

  1. IDEで、サンプルプロジェクト内のルートbuild.gradleに移動します。追加
     run.jvmArgs+="-truffle"
    
  2. 以前に強化されたMicronautフレームワークを公開したMavenローカルリポジトリも追加します。例えば
     repositories {
     mavenLocal()
     ...
     }
    
  3. gradle.propertiesで、公開したMicronautバージョンを更新します。例えば
     micronautVersion=2.5.8-SNAPSHOT
    

    これでセットアップは完了です。

  4. assembleタスクを実行し、定義されたrun Gradleタスクを使用して実行構成を作成します。

  5. 「デバッグ」ボタンを押して、デバッグモードでアプリケーションを起動します。これにより、強化されたホットスワップサポートが有効になります。

  6. アプリケーションが起動したら、http://localhost:8080/conferences/randomにアクセスして、ConferenceControllerからの応答があることを確認します。

  7. サンプルアプリ内のクラスにさまざまな変更を加えてみてください。たとえば、@Controllerマッピングを別の値に変更したり、新しい@Getアノテーション付きメソッドを追加したりして、ホットスワップを適用してその魔法を見てください。新しい@Controllerクラスを定義した場合は、クラスをコンパイルするだけで、ファイルシステムウォッチによって変更が検出されると、明示的にホットスワップする必要なく、リロードが表示されます。

つながりを持ちましょう