可達性元數據

JVM 的動態語言功能 (例如,反射和資源處理) 會在執行時期計算動態存取的程式元素,例如欄位、方法或資源 URL。在 HotSpot 上,這是可行的,因為所有類別檔案和資源都可在執行時期使用,並且可由執行時期載入。所有類別和資源的可用性及其在執行時期的載入會帶來額外的記憶體和啟動時間開銷。

為了使原生二進位檔小型化,native-image 建置器會在建置時期執行靜態分析,以僅判斷應用程式正確性所需的必要程式元素。小型二進位檔允許快速應用程式啟動和低記憶體佔用量,但是它們是有代價的:透過靜態分析判斷動態存取的應用程式元素是不可行的,因為這些元素的可達性取決於在執行時期可用的資料。

為了確保將必要的動態存取元素納入原生二進位檔中,native-image 建置器需要可達性元數據 (以下簡稱元數據)。向建置器提供正確且詳盡的可達性元數據可保證應用程式的正確性,並確保在執行時期與第三方函式庫的相容性。

可以透過以下方式向 native-image 建置器提供元數據

注意:Native Image 正在遷移到更友好的可達性元數據實作,該實作會及早顯示問題,並允許輕鬆除錯。

若要為您的應用程式啟用新的使用者友善的可達性元數據模式,請在建置時期傳遞選項 --exact-reachability-metadata。若要僅為具體套件啟用使用者友善模式,請傳遞 --exact-reachability-metadata=<逗號分隔的套件清單>

若要概觀程式碼中發生遺失註冊的所有位置,而無需承諾確切的行為,您可以在啟動應用程式時傳遞 -XX:MissingRegistrationReportingMode=Warn

若要偵測應用程式意外忽略遺失註冊錯誤 (使用 catch (Throwable t) 區塊) 的位置,請在啟動應用程式時傳遞 -XX:MissingRegistrationReportingMode=Exit。然後,應用程式將無條件列印包含堆疊追蹤的錯誤訊息並立即結束。此行為非常適合執行應用程式測試,以確保包含所有元數據。

反射的使用者友善實作將在未來版本的 GraalVM 中成為預設值,因此及時採用對於避免專案中斷非常重要。

目錄 #

在程式碼中計算元數據 #

可以透過兩種方式在程式碼中計算元數據

  1. 透過將常數引數提供給動態存取 JVM 元素的功能。例如,請參閱以下程式碼中的 Class#forName

     class ReflectiveAccess {
         public Class<Foo> fetchFoo() throws ClassNotFoundException {
             return Class.forName("Foo");
         }
     }
    

    在此,Class.forName("Foo") 會在建置時期評估為常數。建置原生二進位檔時,此值會儲存在其初始堆積中。如果類別 Foo 不存在,則對 Class#forName 的呼叫會轉換為 throw ClassNotFoundException("Foo")

    常數定義為

    • 常值 (例如,"Foo"1)。
    • 存取在建置時期初始化的靜態欄位
    • 存取有效最終變數。
    • 定義長度為常數且所有值都是常數的陣列。
    • 對其他常數進行簡單計算 (例如,"F" + "oo" 或編入陣列)。

    在傳遞常數陣列時,從 native-image 建置器的角度來看,下列宣告和填入陣列的方法是等效的

      Class<?>[] params0 = new Class<?>[]{String.class, int.class};
      Integer.class.getMethod("parseInt", params0);
    
      Class<?>[] params1 = new Class<?>[2];
      params1[0] = Class.forName("java.lang.String");
      params1[1] = int.class;
      Integer.class.getMethod("parseInt", params1);
    
      Class<?>[] params2 = {String.class, int.class};
      Integer.class.getMethod("parseInt", params2);
    

    請注意,Native Image 目前會積極計算常數,因此無法確切指定在建置時期是什麼常數。

  2. 透過在建置時期初始化類別,並將動態存取的元素儲存在原生可執行檔的初始堆積中。當無法使用常數或 JSON 指定元數據時,這種提供元數據的方式適用。在以下情況下,這是必要的

    • 使用者程式碼需要產生新的類別位元組碼。
    • 使用者程式碼需要周遊類別路徑,以計算應用程式所需的動態存取程式元素。

    在以下範例中

     class InitializedAtBuildTime {
         private static Class<?> aClass;
         static {
             try {
                 aClass = Class.forName(readFile("class.txt"));
             } catch (ClassNotFoundException e) {
                 throw RuntimeException(e);
             }
         }
    
         public Class<?> fetchFoo() {
             return aClass;
         }
     }
    

動態存取的元素只有在可透過封閉方法 (例如,InitializedAtBuildTime#fetchFoo) 或靜態欄位 (例如,InitializedAtBuildTime.aClass) 存取堆積的一部分時,才會包含在原生可執行檔的堆積中。

使用 JSON 指定元數據 #

所有在 reachability-metadata.json 檔案中指定的元數據,該檔案位於任何類別路徑項目中 META-INF/native-image/<groupId>/<artifactId>/。可達性元數據的 JSON 結構描述定義於 reachability-metadata-schema-v1.0.0.json

可以在範例區段中找到範例 reachability-metadata.json 檔案。reachability-metadata.json 組態包含單一物件,其中每個元數據類型都有一個欄位。最上層物件中的每個欄位都包含一個元數據項目陣列

{
  "reflection":[],
  "resources":[],
  "bundles":[],
  "serialization":[],
  "jni":[]
}

例如,Java 反射元數據是在 reflection 下指定,而範例項目如下所示

{
  "reflection": [
    {
      "type": "Foo"
    }
  ]
}

條件式元數據項目 #

JSON 型元數據中的每個項目都應為條件式,以避免不必要地增加原生二進位檔大小。條件式項目是透過在項目中新增 condition 欄位以以下方式指定

{
  "condition": {
    "typeReached": "<fully-qualified-class-name>"
  },
  <metadata-entry>
}

具有 typeReached 條件的元數據項目只有在執行時期存取指定的完整類型時,才會在執行時期被視為可用。在此之前,對使用 metadata-entry 表示的元素的所有動態存取行為,就像 metadata-entry 不存在一樣。這表示這些動態存取將會擲回遺失註冊錯誤。

在執行時期,在該類型 (類別或介面) 的類別初始化常式開始之前,或存取任何類型的子類型時,就會存取類型。對於在以下範例中保護元數據項目的 "typeReached": "ConditionType",該類型被視為已存取

class SuperType {
    static {
        // ConditionType reached (subtype reached) => metadata entry available
    }
}
class ConditionType extends SuperType {
    static {
        // ConditionType reached (before static initializer) => metadata entry available
    }
    static ConditionType singleton() {
        // ConditionType reached (already initialized) => metadata entry available
    }
}
public class App {
    public static void main(String[] args) {
        // ConditionType not reached => metadata entry not available
        ConditionType.class;
        // ConditionType not reached (ConditionType.class doesn't start class initialization) => metadata entry not available  
        ConditionType.singleton();
        // ConditionType reached (already initialized) => metadata entry available
    }
}

如果類型標記為 initialize-at-build-time,或其任何子類型標記為 initialize-at-build-time 且它們存在於類別路徑上,也會存取類型。

陣列類型永遠不會標記為已存取,因此無法在條件中使用。

當在建置時期可以存取完整類型時,就會將條件式元數據項目納入映像中。此項目會影響映像大小,並且只有在執行時期達到條件時,才會在執行時期可用。

您可以在 GraalVM 可達性元數據存放庫中找到更多元數據檔案的範例。

元數據類型 #

Native Image 接受下列類型的可達性元數據

  • Java 反射 (java.lang.reflect.* API) 可讓 Java 程式碼在執行時期檢查其自己的類別、方法、欄位及其屬性。
  • JNI 允許原生程式碼在執行時期存取類別、方法、欄位及其屬性。
  • 資源允許動態存取應用程式中類別路徑上的任意檔案。
  • 資源套件 Java 本地化支援 (java.util.ResourceBundle) 可讓 Java 程式碼載入 L10N 資源。
  • 序列化可讓您將 Java 物件寫入 (和讀取) 至 (和從) 串流。
  • (實驗性) 預先定義的類別提供對動態產生類別的支援。

反射 #

對於本節中的所有方法,如果所有呼叫引數都是常數,Native Image 將會在建置時期計算可達性。在程式碼中提供常數引數是提供元數據的慣用方式,因為它不需要在外部 JSON 檔案中重複資訊。

Java 中的反射始於 java.lang.Class,它允許提取更多反射元素,例如方法和欄位。可以透過 java.lang.Class 上的下列靜態函式以反射方式提取類別

  • java.lang.Class forName(java.lang.String) throws java.lang.ClassNotFoundException
  • java.lang.Class forName(java.lang.String, boolean, java.lang.ClassLoader) throws java.lang.ClassNotFoundException
  • java.lang.Class forName(java.lang.Module, java.lang.String)
  • java.lang.Class arrayType() - 需要陣列類型的元數據。也可以透過使用 java.lang.ClassLoader#loadClass(String) 從名稱載入類別,以反射方式提取類別。

若要為以反射方式提取 Class 的呼叫提供元數據,必須將以下項目新增至 reachability-metadata.json 中的 reflection 陣列

{
  "type": "FullyQualifiedReflectivelyAccessedType"
}

對於代理類別,會使用 java.lang.reflect.Proxy 上的下列方法提取 java.lang.Class

  • java.lang.Class getProxyClass(java.lang.ClassLoader, java.lang.Class[]) throws java.lang.IllegalArgumentException
  • java.lang.Object newProxyInstance(java.lang.ClassLoader, java.lang.Class[], java.lang.reflect.InvocationHandler)

代理類別的元數據採用定義代理的介面有序集合形式

{
  "type": {
    "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
  }
}

若在沒有提供元數據的情況下叫用上述方法,將會導致擲回 MissingReflectionRegistrationError,其擴充 java.lang.Error 且不應處理。請注意,即使類型不存在於類別路徑上,上述方法也會擲回 MissingReflectionRegistrationError

如果未為給定的類型提供元數據,java.lang.Class 上的下列方法將會擲回 MissingRegistrationError

  • Constructor getConstructor(Class[]) throws NoSuchMethodException,SecurityException
  • Constructor getDeclaredConstructor(Class[]) throws NoSuchMethodException,SecurityException
  • Constructor[] getConstructors() throws SecurityException
  • Constructor[] getDeclaredConstructors() throws SecurityException
  • Method getMethod(String,Class[]) throws NoSuchMethodException,SecurityException
  • Method getDeclaredMethod(String,Class[]) throws NoSuchMethodException,SecurityException
  • Method[] getMethods() throws SecurityException
  • Method[] getDeclaredMethods() throws SecurityException
  • Field getField(String) throws NoSuchFieldException,SecurityException
  • Field getDeclaredField(String) throws NoSuchFieldException,SecurityException
  • Field[] getFields() throws SecurityException
  • Field[] getDeclaredFields() throws SecurityException
  • RecordComponent[] getRecordComponents()
  • Class[] getPermittedSubclasses()
  • Object[] getSigners()
  • Class[] getNestMembers()
  • Class[] getClasses()
  • Class[] getDeclaredClasses() throws SecurityException

此外,所有透過 java.lang.invoke.MethodHandles.Lookup 進行的反射查找,也必須要有類型 (type) 的中繼資料,否則會拋出 MissingReflectionRegistrationError 錯誤。

請注意,對於 lambda 代理類別,無法提供中繼資料。這是一個已知問題,將在 GraalVM 的未來版本中解決。

反射方法調用 #

要以反射方式調用方法,必須將方法簽名新增至 type 中繼資料。

{
  "type": "TypeWhoseMethodsAreInvoked",
  "methods": [
    {"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
    {"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
  ]
}

為了方便起見,可以在 reachability-metadata.json 中新增以下內容,允許對方法群組進行方法調用:

{
  "type": "TypeWhoseMethodsAreInvoked",
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true
}

allDeclaredConstructorsallDeclaredMethods 允許調用在給定類型上宣告的方法。allPublicConstructorsallPublicMethods 允許調用在類型及其所有超類型上定義的所有公開方法。

如果缺少方法調用的中繼資料,以下方法將會拋出 MissingReflectionRegistrationError 錯誤:

  • java.lang.reflect.Method#invoke(Object, Object...)
  • java.lang.reflect.Constructor#newInstance(Object...)
  • java.lang.invoke.MethodHandle#invokeExact(Object...)
  • java.lang.invoke.MethodHandle#invokeWithArguments (所有重載版本)

反射欄位值存取 #

要以反射方式存取 (取得或設定) 欄位值,必須將欄位名稱的中繼資料新增至類型中。

{
  "type": "TypeWhoseFieldValuesAreAccessed",
  "fields": [{"name": "<fieldName1>"}, {"name": "<fieldNameI>"}, {"name": "<fieldNameN>"}]
}

為了方便起見,可以在 reachability-metadata.json 中新增以下內容,允許存取所有欄位的值:

{
  "type": "TypeWhoseFieldValuesAreAccessed",
  "allDeclaredFields": true,
  "allPublicFields": true
}

allDeclaredFields 允許存取給定類型上宣告的所有欄位,而 allPublicFields 允許存取給定類型及其所有超類型中的所有公開欄位。

如果缺少欄位值存取的中繼資料,以下方法將會拋出 MissingReflectionRegistrationError 錯誤:

  • java.lang.reflect.Field#get(Object)
  • java.lang.reflect.Field#set(Object, Object)
  • java.lang.reflect.VarHandle 上的所有存取方法。

不安全類型配置 #

對於透過 sun.misc.Unsafe#allocateInstance(Class<?>) 或從原生程式碼透過 AllocObject(jClass) 進行的不安全類型配置,我們必須提供以下中繼資料:

{
  "type": "FullyQualifiedUnsafeAllocatedType",
  "unsafeAllocated": true
}

否則,這些方法將會拋出 MissingReflectionRegistrationError 錯誤。

反射中繼資料摘要 #

JSON 中類型的整體定義可以具有以下值:

{
  "condition": {
    "typeReached": "<condition-class>"
  },
  "type": "<class>|<proxy-interface-list>",
  "fields": [
    {"name": "<fieldName>"}
  ],
  "methods": [
    {"name": "<methodName>", "parameterTypes": ["<param-type>"]}
  ],
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true,
  "allDeclaredFields": true,
  "allPublicFields": true,
  "unsafeAllocated": true
}

Java 原生介面 #

Java 原生介面 (JNI) 允許原生程式碼存取任意 Java 類型和類型成員。Native Image 無法預測此類原生程式碼將會查找、寫入或調用哪些內容。要為使用 JNI 存取 Java 值的 Java 應用程式建置原生二進位檔,需要 JNI 中繼資料。

例如,以下 C 程式碼:

jclass clazz = FindClass(env, "jni/accessed/Type");

會查找 jni.accessed.Type 類別,然後可以用來實例化 jni.accessed.Type、調用其方法或存取其欄位。

上述呼叫的中繼資料項目 只能 透過 reachability-metadata.json 提供。請在 jni 欄位中指定 type 項目:

{
  "jni":[
    {
      "type": "jni.accessed.Type"
    }
  ]
}

為類型新增中繼資料並不能透過 GetFieldIDGetStaticFieldIDGetStaticMethodIDGetMethodID 擷取其所有欄位和方法。

若要存取欄位值,我們需要提供欄位名稱。

{
  "type": "jni.accessed.Type",
  "fields": [{"name": "value"}]
}

若要存取所有欄位,可以使用以下屬性:

{
  "type": "jni.accessed.Type",
  "allDeclaredFields": true,
  "allPublicFields": true
}

allDeclaredFields 允許存取給定類型上宣告的所有欄位,而 allPublicFields 允許存取給定類型及其所有超類型中的所有公開欄位。

若要從 JNI 呼叫 Java 方法,我們必須提供方法簽名的中繼資料。

{
  "type": "jni.accessed.Type",
  "methods": [
    {"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
    {"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
  ]
}

為了方便起見,可以透過新增以下內容,允許對方法群組進行方法調用:

{
  "type": "jni.accessed.Type",
  "allDeclaredConstructors": true,
  "allPublicConstructors": true,
  "allDeclaredMethods": true,
  "allPublicMethods": true
}

allDeclaredConstructorsallDeclaredMethods 允許調用在給定類型上宣告的方法。allPublicConstructorsallPublicMethods 允許調用在類型及其所有超類型上定義的所有公開方法。

若要使用 AllocObject 配置類型的物件,中繼資料必須儲存在 reflection 區段中。

{
  "reflection": [
    {
      "type": "jni.accessed.Type",
      "unsafeAllocated": true
    }
  ]
}

若未能為從原生程式碼動態存取的元素提供中繼資料,將會導致例外 (MissingJNIRegistrationError)。

請注意,大多數使用 JNI 的程式庫都無法正確處理例外狀況,因此若要查看遺失哪些元素,必須搭配 -XX:MissingRegistrationReportingMode=Warn 使用 --exact-reachability-metadata

資源 #

Java 能夠存取應用程式類別路徑或模組路徑上的任何資源,前提是請求程式碼具有存取權限。資源中繼資料會指示 native-image 建置器將指定的資源和資源組合包含在產生的二進位檔中。這種方法的一個後果是,使用資源進行配置(例如記錄)的應用程式某些部分實際上是在建置時配置的。

Native Image 將會偵測到對 java.lang.Class#getResourcejava.lang.Class#getResourceAsStream 的呼叫,其中:

  • 呼叫這些方法的類別是常數。
  • 第一個引數 (name) 是常數,並且會自動註冊這些資源。

以下程式碼可以立即運作,因為:

  • 它使用類別字面值 (Example.class) 作為接收者。
  • 它使用字串字面值作為 name 參數。
    class Example {
     public void conquerTheWorld() {
         InputStream plan = Example.class.getResourceAsStream("plans/v2/conquer_the_world.txt");
     }
    }
    

JSON 中的資源中繼資料 #

資源中繼資料是在 reachability-metadata.json 檔案的 resources 欄位中指定。以下是資源中繼資料的範例:

{
  "resources": [
    {
      "glob": "path1/level*/**"
    }
  ]
}

glob 欄位使用 glob 模式規則的子集來指定資源。指定資源路徑時,需要遵守以下幾條規則:

  • native-image 工具僅支援星號 (*) 和 globstar (**) 萬用字元模式。
    • 根據定義,星號 可以比對一個層級上的任意數量的任意字元,而 globstar 可以比對任何層級上的任意數量的字元。
    • 如果需要按字面意義處理星號 (沒有特殊意義),可以使用 \ 進行跳脫 (例如 \*)。
  • 在 glob 中,層級 代表以 / 分隔的模式部分。
  • 撰寫 glob 模式時,必須遵守以下規則:
    • Glob 不得為空 (例如 "")。
    • Glob 不得以結尾斜線 (/) 結尾 (例如 "foo/bar/")。
    • Glob 在一個層級上不能包含超過兩個連續的 (未跳脫的) * 字元 (例如 "foo/***/")。
    • Glob 不能包含空層級 (例如 "foo//bar")。
    • Glob 不能包含兩個連續的 globstar 萬用字元 (例如 "foo/**/**")。
    • Glob 在與 globstar 萬用字元相同的層級上不能有其他內容 (例如 "foo/**bar/x")。

假設有以下專案結構:

app-root
└── src
    └── main
        └── resources
            ├── Resource0.txt
            └── Resource1.txt

您可以:

  • 使用 glob **/Resource*.txt 包含所有資源 ({ "glob":})。
  • 使用 glob **/Resource0.txt 包含 Resource0.txt
  • 使用 globs **/Resource0.txt**/Resource1.txt 包含 Resource0.txtResource1.txt

Java 模組中的資源 #

對於每個資源或資源組合,可以指定應從哪個模組取得資源或資源組合。您可以在每個項目的獨立 module 欄位中指定模組名稱。例如:

{
   "resources": [
      {
        "module:": "library.module",
        "glob": "resource-file.txt" 
      }
   ]
}

這將導致 native-image 工具僅包含 Java 模組 library.module 中的 resource-file.txt。如果其他模組或類別路徑包含符合模式 resource-file.txt 的資源,則只會註冊 library-module 中的資源以包含在原生可執行檔中。Native Image 也會確保模組在執行階段保證可存取。

以下程式碼模式:

InputStream resource = ModuleLayer.boot().findModule("library.module").getResourceAsStream(resourcePath);

對於如上所述註冊的資源,它將始終按預期運作 (即使模組不包含任何被靜態分析視為可連線的程式碼)。

內嵌資源資訊 #

有兩種方式可以查看哪些資源包含在原生可執行檔中:

  1. 使用選項 --emit build-report 為您的原生可執行檔產生建置報告。您可以在其中的 Resources 索引標籤下找到有關所有包含資源的資訊。
  2. 使用選項 -H:+GenerateEmbeddedResourcesFile 產生 JSON 檔案 embedded-resources.json,其中列出所有包含的資源。

對於每個註冊的資源,您會取得:

  • 模組 (如果資源不屬於任何模組,則為 unnamed)。
  • 名稱 (資源路徑)。
  • 來源 (資源在系統上的位置)。
  • 類型 (資源是檔案、目錄還是遺失)。
  • 大小 (實際資源大小)。

注意:資源目錄的大小僅代表所有目錄項目的名稱大小 (而非內容大小的總和)。

資源組合 #

Java 本地化支援 (java.util.ResourceBundle) 能夠載入 L10N 資源,並顯示針對特定 地區設定 本地化的訊息。Native Image 需要知道您的應用程式使用的資源組合,以便將適當的資源和程式元素包含到應用程式中。

可以在 reachability-metadata.jsonbundles 區段中指定簡單的組合:

{
  "bundles": [
    {
      "name":"your.pkg.Bundle"
    }
  ]
}

若要從特定模組請求組合:

{
  "bundles": [
    {
      "name":"app.module:module.pkg.Bundle"
    }
  ]
}

預設情況下,資源包會包含所有已包含在映像檔中的語言環境。以下範例說明如何僅為一個資源包包含特定的語言環境

{
  "bundles": [
    {
      "name": "specific.locales.Bundle",
      "locales": ["en", "de", "sk"]
    }
  ]
}

語言環境 #

也可以指定原生可執行檔中應包含哪些語言環境,以及哪個應該是預設的語言環境。例如,若要將預設語言環境切換為瑞士德語,並同時包含法語和英語,請使用以下選項

native-image -Duser.country=CH -Duser.language=de -H:IncludeLocales=fr,en

語言環境是使用語言標籤指定的。您可以使用 -H:+IncludeAllLocales 來包含所有語言環境,但請注意,這會增加產生的可執行檔大小。

序列化 #

Java 可以序列化(或反序列化)任何實作 Serializable 介面的類別。Native Image 支援使用適當的序列化元資料註冊進行序列化(或反序列化)。這是必要的,因為序列化通常需要對正在序列化的物件進行反射存取。

程式碼中的序列化元資料註冊 #

Native Image 會偵測對 ObjectInputFilter.Config#createFilter(String pattern) 的呼叫,如果 pattern 引數是常數,則會註冊模式中提及的確切類別以進行序列化。例如,以下模式將註冊 pkg.SerializableClass 類別以進行序列化

  var filter = ObjectInputFilter.Config.createFilter("pkg.SerializableClass;!*;")
  objectInputStream.setObjectInputFilter(proof);

使用此模式具有在 JVM 上提高安全性的正面副作用,因為只有 pkg.SerializableClass 可以被 objectInputStream 接收。

萬用字元模式僅針對封閉類別的 lambda 代理類別執行序列化註冊。例如,若要在封閉類別 pkg.LambdaHolder 中註冊 lambda 序列化,請使用

  ObjectInputFilter.Config.createFilter("pkg.LambdaHolder$$Lambda*;")

"pkg.**""pkg.Prefix*" 這樣的模式不會執行序列化註冊,因為它們太過廣泛,會顯著增加映像檔大小。

對於呼叫 sun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class)sun.reflect.ReflectionFactory#newConstructorForSerialization(java.lang.Class, ),當所有引數和接收者都是常數時,原生映像檔會偵測到對這些函式的呼叫。例如,以下呼叫將註冊 SerializlableClass 以進行序列化

  ReflectionFactory.getReflectionFactory().newConstructorForSerialization(SerializableClass.class);

若要建立用於序列化的自訂建構子,請使用

  var constructor = SuperSuperClass.class.getDeclaredConstructor();
  var newConstructor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(BaseClass.class, constructor);

代理類別只能透過 JSON 檔案註冊以進行序列化。

JSON 中的序列化元資料 #

序列化元資料在 reachability-metadata.jsonserialization 區段中指定。

若要指定一般的 serialized.Type,請使用

{
  "serialization": [
    {
      "type": "serialized.Type"
    }
  ]
}

若要指定用於序列化的代理類別,請使用以下項目

{
  "serialization": [
    {
      "type": {
        "proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
      }
    }
  ]
}

在極少數情況下,應用程式可能會明確呼叫

    ReflectionFactory.newConstructorForSerialization(Class<?> cl, Constructor<?> constructorToCall);

其中傳遞的 constructorToCallcl 的常規序列化會自動使用的有所不同。

為了也支援這類序列化用例,可以註冊具有自訂 constructorToCall 的類別以進行序列化。例如,為了允許序列化 org.apache.spark.SparkContext$$anonfun$hadoopFile$1,請使用 java.lang.Object 的宣告建構子作為自訂的 targetConstructor,請使用

{
  "serialization": [
    {
      "type": "<fully-qualified-class-name>",
      "customTargetConstructorClass": "<custom-target-constructor-class>"
    }
  ]
}

範例可達性元資料 #

以下是您可以在 reachabilty-metadata.json 中使用的範例可達性元資料組態

{
  "reflection": [
    {
      "type": "reflectively.accessed.Type",
      "fields": [
        {
          "name": "field1"
        }
      ],
      "methods": [
        {
          "name": "method1",
          "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"] 
        }
      ],
      "allDeclaredConstructors": true,
      "allPublicConstructors": true,
      "allDeclaredFields": true,
      "allPublicFields": true,
      "allDeclaredMethods": true,
      "allPublicMethods": true,
      "unsafeAllocated": true
    }
  ],
  "jni": [
    {
      "type": "jni.accessed.Type",
      "fields": [
        {
          "name": "field1"
        }
      ],
      "methods": [
        {
          "name": "method1",
          "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]
        }
      ],
      "allDeclaredConstructors": true,
      "allPublicConstructors": true,
      "allDeclaredFields": true,
      "allPublicFields": true,
      "allDeclaredMethods": true,
      "allPublicMethods": true
    }
  ],
  "resources": [
    {
      "module": "optional.module.of.a.resource",
      "glob": "path1/level*/**"
    }
  ],
  "bundles": [
    {
      "name": "fully.qualified.bundle.name",
      "locales": ["en", "de", "other_optional_locales"]
    }
  ],
  "serialization": [
    {
      "type": "serialized.Type",
      "customTargetConstructorClass": "optional.serialized.super.Type"
    }
  ]
}

在執行階段定義類別 #

Java 支援在執行階段從位元組碼載入新的類別,這在 Native Image 中是不可能的,因為所有類別都必須在建置時已知(「封閉世界假設」)。為了克服這個問題,有以下選項

  1. 修改或重新設定您的應用程式(或第三方程式庫),使其不會在執行階段產生類別,或透過非內建的類別載入器載入類別。
  2. 如果必須產生類別,請嘗試在專用類別的靜態初始化器中於建置時產生它們。產生的 java.lang.Class 物件應儲存在靜態欄位中,並透過將 --initialize-at-build-time=<class_name> 作為建置引數來初始化專用類別。
  3. 如果以上皆不適用,請使用 Native Image Agent 來執行應用程式,並使用 java -agentlib:native-image-agent=config-output-dir=<config-dir>,experimental-class-define-support <application-arguments> 收集預定義的類別。在執行階段,如果嘗試載入與追蹤期間遇到的類別之一具有相同名稱和位元組碼的類別,則會將預定義的類別提供給應用程式。

預定義類別元資料是在 predefined-classes-config.json 檔案中指定,並符合 predefined-classes-config-schema-v1.0.0.json 中定義的 JSON 結構描述。該結構描述還包含有關此組態如何運作的更多詳細資訊和說明。以下是 predefined-classes-config.json 的範例

[
  {
    "type": "agent-extracted",
    "classes": [
      {
        "hash": "<class-bytecodes-hash>",
        "nameInfo": "<class-name"
      }
    ]
  }
]

注意:預定義類別元資料不應手動撰寫。注意:預定義類別是舊專案的最佳努力方法,且無法保證它們能正常運作。

延伸閱讀 #

與我們聯繫