備註:

瞭解反射和 GraalVM 原生映像檔

簡介

這個實驗室是專為尋求深入瞭解反射在 GraalVM 原生映像檔內部運作的開發者所設計。

GraalVM 原生映像檔可將 Java 應用程式預先編譯成獨立的原生執行檔。若使用「GraalVM 原生映像檔」,則只會在程式實際執行時將應用程式所需的程式碼加到原生執行檔中。

這些原生執行檔有一些重要的優點,就是在:

許多頂尖的微服務架構採用 GraalVM 原生映像檔,包括 Micronaut、Spring、Helidon 及 Quarkus,將提前編譯支援。

此外,Maven 和 Gradle 外掛程式可讓原生映像檔建立、測試及執行 Java 應用程式,就像原生執行檔一樣簡單。

注意:Oracle Cloud Infrastructure (OCI) 提供 GraalVM Enterprise,無須額外付費。

預估實驗室時間:30 分鐘

實驗室目標

在此實驗室中,您將會執行下列作業:

附註:每當您看到膝上型電腦圖示時,就需要執行一些動作。請謹慎瞭解這些原則。

# This is where we you will need to do something

您的開發環境是由遠端主機提供:配備 Oracle Linux 8、1 CPU 及 32GB 記憶體的 OCI 運算執行處理。
Luna Labs 桌上型電腦環境將在遠端主機就緒前顯示,最多可能需要兩分鐘的時間。

STEP 1:連線至虛擬主機並檢查開發環境

您可以透過在 Luna 桌面環境中執行設定命令檔,來連線至您的遠端主機。此命令檔可透過資源頁籤使用

  1. 在桌面上,按兩下 Luna-Lab.html 圖示。就會開啟此頁面,顯示實驗室特定的 Oracle Cloud Infrastructure 證明資料和資訊。

  2. 將會顯示資源頁籤。請注意,資源標題旁邊的主題將隨著佈建在茶科技雲中的運算執行處理而衍生。

  3. 佈建執行處理時,這最多可能需要 2 分鐘的時間,您將會在資源頁籤看到以下內容

    Luna 資源頁籤

  4. 從資源頁籤複製設定 VS 程式碼環境的組態命令檔。按一下檢視詳細資訊連結以顯示組態。複製此項目,如以下螢幕擷取畫面所示。

    複製組態指令碼

  5. 開啟終端機,如以下螢幕快照所示:

    開啟終端機

  6. 將組態代碼貼至終端機,該終端機會為您開啟 VS 代碼。

    貼上終端機 1

    貼上終端機 2

就已完成!恭喜,您現在已經順利連線到 Oracle Cloud 中的遠端主機!

STEP 2:已關閉的世界假設

使用 GraalVM 隨附的 native-image 工具建立獨立執行檔,與建置 Java 應用程式並不相同。原生影像會使用稱為封閉式世界假設的內容。

「關閉的世界」假設意謂著,應用程式在程式實際執行時呼叫的所有位元組碼必須在建置時 (觀察和分析),即 native-image 工具建立獨立執行檔時。

在繼續之前,建議您先針對使用 GraalVM 原生映像檔建置的應用程式,進行建置 / 執行模型。

  1. 將您的 Java 原始程式碼編譯成 Java 位元組程式碼類別
  2. 使用 native-image 工具,將這些 Java 位元組程式碼類別建置成原生執行檔
  3. 執行原生執行檔

但在步驟 2 中該怎麼樣?

首先,native-image 工具會執行分析,以查看您應用程式內的哪些類別可以連線。我們很快就會更詳細查看這個內容。

其次,所找到的類別是已知可起始 (自動初始化安全類別) 的類別。起始類別的類別資料會載入影像堆集中,然後會儲存到獨立的可執行檔 (在文字區段中)。這是 GraalVM native-image 工具中的一項功能,可為這類快速入門應用系統提供這些功能。

附註:這與「物件」初始化不同。在原生執行檔的程式實際執行期間,會發生物件初始化。

我們說我們將返回實現性的主題。如前所述,分析會決定需要包含在獨立執行檔中的類別、方法與欄位。分析為靜態的,也就是不會執行此程式碼。分析可以判斷動態類別載入與使用反射的一些案例 (請參閱),不過在某些情況下,無法加以處理。

為了處理 Java 的動態功能,您必須瞭解哪些類別使用反射,或者哪些類別已載入動態功能。

讓我們來看範例。

STEP 3:使用反射的範例

想像一下您具有下列類別:ReflectionExample.java (可在目錄中找到一份副本,demo/ReflectionExample.java):

import java.lang.reflect.Method;

class StringReverser {
    static String reverse(String input) {
        return new StringBuilder(input).reverse().toString();
    }
}

class StringCapitalizer {
    static String capitalize(String input) {
        return input.toUpperCase();
    }
}

public class ReflectionExample {
    public static void main(String[] args) throws ReflectiveOperationException {
        String className = args[0];
        String methodName = args[1];
        String input = args[2];

        Class<?> clazz = Class.forName(className);
        Method method = clazz.getDeclaredMethod(methodName, String.class);
        Object result = method.invoke(null, input);
        System.out.println(result);
    }
}

首先,在 VS 代碼中建立終端機。終端機 > 新建終端機

接著,讓我們建立程式碼。在您的 Shell 中,從 VS 程式碼內執行下列命令:

javac ReflectionExample.java

類別 ReflectionExample 中的 main 方法會載入名稱被傳入為引數的類別,這是非常動態的使用案例!類別的第二個引數是應呼叫之動態載入類別的方法名稱。

讓我們執行並瞭解執行什麼工作。

java ReflectionExample StringReverser reverse "hello"

按照預期,透過反射取得 StringReverser 類別上的方法 reverse。已呼叫此方法,並回轉我們的輸入字串 "hello"。到目前為止,很好。

好的,但是如果我們嘗試在程式外建立原生影像,會發生什麼事?試試吧。在您的 Shell 中執行下列命令:

native-image --no-fallback ReflectionExample

附註:--no-fallback 選項至 native-image 會導致建置無法建置獨立原生 executabale 時失敗。

現在,讓我們執行產生的原生執行檔,然後看看它如何:

./reflectionexample StringReverser reverse "hello"

Exception in thread "main" java.lang.ClassNotFoundException: StringReverser
	at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
	at java.lang.Class.forName(DynamicHub.java:1214)
	at ReflectionExample.main(ReflectionExample.java:21)

此處發生什麼情況?似乎我們原生執行檔找不到 StringReverser 類別。這如何發生?從現在起,我想我們也許會有什麼想法。本屆世界立場假設。

在進行 native-image 工具的分析期間,無法判斷曾使用 StringReverser 類別。因此它會將類別從其產生的原生執行檔中移除。注意:從獨立執行檔移除不需要的類別,即可縮小僅包括已知可使用之類別的工具。我們剛才發現,這可能會引發反思的問題,但還是幸運,有辦法解決這個問題。

STEP 4:介紹原生影像反射組態

我們可以透過特殊的組態檔案,告訴 native-image 建置關於反映實例的工具。這些檔案會以 JSON 撰寫,並且可透過使用旗標傳送至 native-image 工具。

因此,我們可以將哪些其他類型的組態資訊傳送至 native-image 建置工具?工具目前支援讀取包含下列詳細資訊的檔案:

我們只想看這個實驗室中如何處理反射,所以我們將著重在該處。

以下是這些檔案的範例 (從此處進行):

[
  {
    "name" : "java.lang.Class",
    "queryAllDeclaredConstructors" : true,
    "queryAllPublicConstructors" : true,
    "queryAllDeclaredMethods" : true,
    "queryAllPublicMethods" : true,
    "allDeclaredClasses" : true,
    "allPublicClasses" : true
  },
  {
    "name" : "java.lang.String",
    "fields" : [
      { "name" : "value" },
      { "name" : "hash" }
    ],
    "methods" : [
      { "name" : "<init>", "parameterTypes" : [] },
      { "name" : "<init>", "parameterTypes" : ["char[]"] },
      { "name" : "charAt" },
      { "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
    ]
  },
  {
    "name" : "java.lang.String$CaseInsensitiveComparator",
    "queriedMethods" : [
      { "name" : "compare" }
    ]
  }
]

因此,我們可以看到需要透過反映 API 存取的類別和方法進行設定。我們可以自行設定,但產生這些組態檔最便利的方式是使用輔助組態 javaagent

STEP 5:原生影像、輔助組態:輸入 Java 代理程式

可以從頭開始寫入完整的反射組態檔,但 GraalVM Java 程式實際執行提供 java 追蹤代理程式 javaagent,這會在您執行應用程式時自動為您產生。

我們試試一下。

在啟用追蹤代理程式的情況下執行應用程式。在我們的 Shell 中,請執行以下動作:

# Note: the tracing agent parameter must come before the classpath and jar params on the command ine
java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"

追蹤代理程式組態

讓我們看看所建立的組態:

cat META-INF/native-image/reflect-config.json
[
    {
    "name":"StringReverser",
    "methods":[{"name":"reverse","parameterTypes":["java.lang.String"] }]
    }
]

若您指定 native-image-agent=config-merge-dir,您可以執行此程序多重時間,並合併執行,如以下範例所示:

java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"

建立獨立執行檔現在將使用提供的組態。讓我們建立:

native-image --no-fallback ReflectionExample

讓我們來看看它的效果更好:

./reflectionexample StringReverser reverse "hello"

是!

結論 @ info:tooltip

使用「GraalVM 原生映像檔」建置獨立執行檔取決於「封閉式世界」假設,當建立獨立執行檔時,我們必須事先知道,瞭解程式碼中可能發生的任何反映情況。

GraalVM 平台提供使用反映時,對 native-image 組建工具指定的方法。注意:在某些簡單情況下,native-image 工具可自行尋找。

GraalVM 平台也提供透過 Java 追蹤代理程式探索反射和其他動態行為的方法,並且會自動產生 native-image 工具所需的組態檔。

使用追蹤代理程式時,請務必注意一些事項:

我們希望您享受此自學課程,並瞭解使用「原生影像」時,我們如何處理反射。

深入瞭解

其他學習資源

探索 docs.oracle.com/learn 上的其他實驗室,或是存取更多免費學習內容至 Oracle Learning YouTube 通道。此外,瀏覽 education.oracle.com/learning-explorer 以成為 Oracle Learning Explorer。

如需產品文件,請瀏覽 Oracle Help Center