使用 Espresso 強化熱插拔功能

使用 Espresso,您可以從強化的熱插拔功能中受益,該功能允許程式碼在開發過程中自然演進,而無需重新啟動正在執行的應用程式。除了在除錯模式下啟動您的應用程式並連接標準 IDE 除錯器以獲得強化熱插拔的優勢外,您無需配置任何特定設定。

使用 Espresso 進行除錯 #

您可以使用您最喜歡的 IDE 除錯器來除錯在 Espresso 執行階段中執行的 Java 應用程式。例如,從 IntelliJ IDEA 啟動除錯器工作階段是基於執行組態。為了確保您在相同的環境中將除錯器連接到您的 Java 應用程式,請在主選單中導覽至 RunDebug…Edit Configurations,展開 Environment,檢查 JRE 值和 VM 選項值。它應該顯示 GraalVM 作為專案的 JRE,而 VM 選項應包含 -truffle -XX:+IgnoreUnrecognizedVMOptions。必須指定 -XX:+IgnoreUnrecognizedVMOptions,因為 Intellij 會自動新增一個 -javaagent 引數,目前尚不支援該引數。按下 Debug。

這將執行應用程式並在背景啟動除錯器工作階段。

在除錯工作階段期間使用熱插拔 #

一旦您的除錯器工作階段正在執行,您將能夠應用廣泛的程式碼變更(熱插拔),而無需重新啟動工作階段。您可以隨意在您自己的應用程式上嘗試,或按照以下指示進行

  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 執行並按下 Debug。您將看到

    HotSwap Debugging Session: Debug Output

  6. 當停在斷點時,從 runDemo() 的主體中提取一個方法

    HotSwap Debugging Session: Extract Method

  7. 透過導覽至 Run -> Debugging Actions -> Reload Changed Classes 來重新載入變更

    HotSwap Debugging Session: Reload Changed Classes

  8. 透過注意到 Debug -> Frames 檢視中的 <obsolete>:-1 目前框架,來驗證變更已套用

    HotSwap Debugging Session: Frames View

  9. 在新提取的方法的第一行放置一個斷點,然後按下 Resume Program。將會觸發斷點

    HotSwap Debugging Session: Set a Breakpoint and Resume Program

  10. 嘗試將 printRandom() 的存取修飾詞從 private 變更為 public static。重新載入變更。按下 Resume Program 以驗證變更已套用

    HotSwap Debugging Session: Change Access Modifiers

觀看影片 使用 Espresso 強化熱插拔功能示範的版本

支援的變更 #

Espresso 的強化熱插拔功能幾乎已完整。支援以下變更

  • 新增和移除方法
  • 新增和移除建構子
  • 從介面新增和移除方法
  • 變更方法的存取修飾詞
  • 變更建構子的存取修飾詞
  • 新增和移除欄位
  • 變更欄位類型
  • 在階層中移動欄位並保留狀態 (請參閱以下注意事項)
  • 變更類別存取修飾詞,例如,abstract 和 final 修飾詞
  • 變更 Lambdas
  • 新增新的匿名內部類別
  • 移除匿名內部類別
  • 變更父類別
  • 變更已實作的介面

注意事項:當實例欄位在類別階層中移動時,只要可能,就會保留狀態。範例包括上拉欄位重構,其中原始子類別的所有現有實例都能夠從父類別欄位讀取先前儲存的值。另一方面,對於與該變更之前不存在欄位的子類別實例無關的實例,新的欄位值將是語言預設值 (物件類型欄位為 null,int 為 0,依此類推)。

以下限制仍然存在

  • 變更 Enums

熱插拔外掛程式 API #

使用 Espresso,您可以從強化的熱插拔功能中受益,該功能允許程式碼在開發過程中自然演進,而無需重新啟動正在執行的應用程式。雖然程式碼重新載入 (熱插拔) 是一個強大的工具,但它不足以反映所有類型的變更,例如,對註解、框架特定變更 (例如,已實作的服務或 beans) 的變更。對於這些情況,通常需要執行程式碼才能重新載入組態或環境定義,變更才能完全反映在執行中的實例中。這就是 Espresso 熱插拔外掛程式 API 發揮作用的地方。

熱插拔外掛程式 API 旨在讓框架開發人員設定適當的掛鉤,以反映您 IDE 中原始碼編輯所產生的變更。主要設計原則是您可以註冊各種熱插拔接聽程式,這些接聽程式將在指定的熱插拔事件上觸發。範例包括重新執行靜態初始化器的能力、一般後熱插拔回呼,以及當特定服務提供者的實作變更時的掛鉤。

注意事項:熱插拔外掛程式 API 正在開發中,並且可能會根據社群的要求新增更細微的熱插拔接聽程式註冊。我們歡迎您透過我們的社群支援 管道傳送增強功能要求,以協助塑造 API。

透過執行範例來檢閱熱插拔外掛程式 API,該範例將在 Micronaut 上啟用更強大的重新載入支援。

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,會執行 Micronaut 特定的程式碼,以在 reloadContext 方法中重新載入應用程式環境定義。

偵測並注入新類別 #

熱插拔旨在讓類別在執行中的應用程式中進行熱插拔。但是,如果開發人員引入一個全新的類別 (例如,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. 按下 Debug 按鈕以在除錯模式下啟動應用程式,這會啟用強化的熱插拔支援。

  6. 應用程式啟動後,請前往 http://localhost:8080/conferences/random 來驗證您是否從 ConferenceController 收到回應。

  7. 試著對範例應用程式中的類別進行各種變更,例如,將 @Controller 的映射變更為不同的值,或是新增一個帶有 @Get 註釋的方法,然後應用 HotSwap 來看看奇蹟。如果您定義了一個新的 @Controller 類別,您只需要編譯該類別,一旦檔案系統監看器偵測到變更,您就會看到重新載入,而不需要明確地使用 HotSwap。

與我們聯繫