- 適用於 JDK 23 的 GraalVM (最新版)
- 適用於 JDK 24 的 GraalVM (搶先體驗版)
- 適用於 JDK 21 的 GraalVM
- 適用於 JDK 17 的 GraalVM
- 封存
- 開發組建
可達性元數據
JVM 的動態語言功能 (例如,反射和資源處理) 會在執行時期計算動態存取的程式元素,例如欄位、方法或資源 URL。在 HotSpot 上,這是可行的,因為所有類別檔案和資源都可在執行時期使用,並且可由執行時期載入。所有類別和資源的可用性及其在執行時期的載入會帶來額外的記憶體和啟動時間開銷。
為了使原生二進位檔小型化,native-image
建置器會在建置時期執行靜態分析,以僅判斷應用程式正確性所需的必要程式元素。小型二進位檔允許快速應用程式啟動和低記憶體佔用量,但是它們是有代價的:透過靜態分析判斷動態存取的應用程式元素是不可行的,因為這些元素的可達性取決於僅在執行時期可用的資料。
為了確保將必要的動態存取元素納入原生二進位檔中,native-image
建置器需要可達性元數據 (以下簡稱元數據)。向建置器提供正確且詳盡的可達性元數據可保證應用程式的正確性,並確保在執行時期與第三方函式庫的相容性。
可以透過以下方式向 native-image
建置器提供元數據
- 透過在建置原生二進位檔時在程式碼中計算元數據,並將所需的元素儲存在原生二進位檔的初始堆積中。
- 透過提供儲存在類別路徑上 META-INF/native-image/<groupId>/<artifactId>/ 目錄中的 reachability-metadata.json 檔案。如需有關如何自動為您的應用程式收集元數據的詳細資訊,請參閱自動收集元數據。
- 對於需要類別路徑掃描或建置時期初始化的更進階使用案例,可使用 Native Image Feature API。
注意:Native Image 正在遷移到更友好的可達性元數據實作,該實作會及早顯示問題,並允許輕鬆除錯。
若要為您的應用程式啟用新的使用者友善的可達性元數據模式,請在建置時期傳遞選項
--exact-reachability-metadata
。若要僅為具體套件啟用使用者友善模式,請傳遞--exact-reachability-metadata=<逗號分隔的套件清單>
。若要概觀程式碼中發生遺失註冊的所有位置,而無需承諾確切的行為,您可以在啟動應用程式時傳遞
-XX:MissingRegistrationReportingMode=Warn
。若要偵測應用程式意外忽略遺失註冊錯誤 (使用
catch (Throwable t)
區塊) 的位置,請在啟動應用程式時傳遞-XX:MissingRegistrationReportingMode=Exit
。然後,應用程式將無條件列印包含堆疊追蹤的錯誤訊息並立即結束。此行為非常適合執行應用程式測試,以確保包含所有元數據。反射的使用者友善實作將在未來版本的 GraalVM 中成為預設值,因此及時採用對於避免專案中斷非常重要。
目錄 #
在程式碼中計算元數據 #
可以透過兩種方式在程式碼中計算元數據
-
透過將常數引數提供給動態存取 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 目前會積極計算常數,因此無法確切指定在建置時期是什麼常數。
- 常值 (例如,
-
透過在建置時期初始化類別,並將動態存取的元素儲存在原生可執行檔的初始堆積中。當無法使用常數或 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
}
allDeclaredConstructors
和 allDeclaredMethods
允許調用在給定類型上宣告的方法。allPublicConstructors
和 allPublicMethods
允許調用在類型及其所有超類型上定義的所有公開方法。
如果缺少方法調用的中繼資料,以下方法將會拋出 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"
}
]
}
為類型新增中繼資料並不能透過 GetFieldID
、GetStaticFieldID
、GetStaticMethodID
和 GetMethodID
擷取其所有欄位和方法。
若要存取欄位值,我們需要提供欄位名稱。
{
"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
}
allDeclaredConstructors
和 allDeclaredMethods
允許調用在給定類型上宣告的方法。allPublicConstructors
和 allPublicMethods
允許調用在類型及其所有超類型上定義的所有公開方法。
若要使用 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#getResource
和 java.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"
)。
- Glob 不得為空 (例如
假設有以下專案結構:
app-root
└── src
└── main
└── resources
├── Resource0.txt
└── Resource1.txt
您可以:
- 使用 glob
**/Resource*.txt
包含所有資源 ({ "glob":}
)。 - 使用 glob
**/Resource0.txt
包含 Resource0.txt。 - 使用 globs
**/Resource0.txt
和**/Resource1.txt
包含 Resource0.txt 和 Resource1.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);
對於如上所述註冊的資源,它將始終按預期運作 (即使模組不包含任何被靜態分析視為可連線的程式碼)。
內嵌資源資訊 #
有兩種方式可以查看哪些資源包含在原生可執行檔中:
- 使用選項
--emit build-report
為您的原生可執行檔產生建置報告。您可以在其中的Resources
索引標籤下找到有關所有包含資源的資訊。 - 使用選項
-H:+GenerateEmbeddedResourcesFile
產生 JSON 檔案 embedded-resources.json,其中列出所有包含的資源。
對於每個註冊的資源,您會取得:
- 模組 (如果資源不屬於任何模組,則為
unnamed
)。 - 名稱 (資源路徑)。
- 來源 (資源在系統上的位置)。
- 類型 (資源是檔案、目錄還是遺失)。
- 大小 (實際資源大小)。
注意:資源目錄的大小僅代表所有目錄項目的名稱大小 (而非內容大小的總和)。
資源組合 #
Java 本地化支援 (java.util.ResourceBundle
) 能夠載入 L10N 資源,並顯示針對特定 地區設定 本地化的訊息。Native Image 需要知道您的應用程式使用的資源組合,以便將適當的資源和程式元素包含到應用程式中。
可以在 reachability-metadata.json 的 bundles
區段中指定簡單的組合:
{
"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.json 的 serialization
區段中指定。
若要指定一般的 serialized.Type
,請使用
{
"serialization": [
{
"type": "serialized.Type"
}
]
}
若要指定用於序列化的代理類別,請使用以下項目
{
"serialization": [
{
"type": {
"proxy": ["FullyQualifiedInterface1", "...", "FullyQualifiedInterfaceN"]
}
}
]
}
在極少數情況下,應用程式可能會明確呼叫
ReflectionFactory.newConstructorForSerialization(Class<?> cl, Constructor<?> constructorToCall);
其中傳遞的 constructorToCall
與 cl
的常規序列化會自動使用的有所不同。
為了也支援這類序列化用例,可以註冊具有自訂 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 中是不可能的,因為所有類別都必須在建置時已知(「封閉世界假設」)。為了克服這個問題,有以下選項
- 修改或重新設定您的應用程式(或第三方程式庫),使其不會在執行階段產生類別,或透過非內建的類別載入器載入類別。
- 如果必須產生類別,請嘗試在專用類別的靜態初始化器中於建置時產生它們。產生的 java.lang.Class 物件應儲存在靜態欄位中,並透過將
--initialize-at-build-time=<class_name>
作為建置引數來初始化專用類別。 - 如果以上皆不適用,請使用 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"
}
]
}
]
注意:預定義類別元資料不應手動撰寫。注意:預定義類別是舊專案的最佳努力方法,且無法保證它們能正常運作。