来自 Gson 的意外行为
Unexpected behaviour from Gson
我开发了一个存储来自设备的数据的小应用程序:我选择以 JSON 格式存储数据,数据的 serialization/deserialization 工作正常,即使它涉及一些我创建的自定义类型...但只有我在 IDE(就此而言 Eclipse)工作。
当我导出一个 运行nable JAR 文件时,数据的反序列化遇到了某种问题,因为软件总是抛出这个异常:
Caused by: java.lang.UnsupportedOperationException: Cannot allocate class LocalDateTime
at com.google.gson.internal.UnsafeAllocator.newInstance(UnsafeAllocator.java:104)
at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:225)
... 88 common frames omitted
我以为我会遇到自定义类型的问题,而不是内置类型。此时,我发现了两件事:
- 如果我对 运行 JAR 文件使用完整的 JRE 9,则不会抛出异常:我仔细检查了我用 Jlink.exe 创建的自定义 JRE 中包含的模块,一切都是正确包含。我仍然想使用较小的 JRE,所以我还没有进一步调查(我猜这解释了为什么在 IDE 它完美运行)
- 我向 Gson 对象添加了一个自定义反序列化器(见下文),我只是手动将 JSON 字符串转换为有效数据,这避免了 LocalDateTime class 上的异常...但异常再次出现在另一个 class 上,这次是定制的。
此时,我想我可以简单地为导致问题的每种数据类型添加一个反序列化器,但我想知道 为什么问题不会发生在完整的 JRE 上,以及为什么较小的 JRE 会导致此 ,即使包含所有必需的模块也是如此。也许还值得一提的是,我没有向保存数据的 Gson 对象添加自定义序列化程序,它全部按照 Gson 默认序列化。
LocalDateTime
解串器:
@Override
public LocalDateTime deserialize(JsonElement json, java.lang.reflect.Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject joDate = json.getAsJsonObject().get("date").getAsJsonObject();
JsonObject joTime = json.getAsJsonObject().get("time").getAsJsonObject();
//JSON example: {"date":{"year":2019,"month":1,"day":9},"time":{"hour":6,"minute":14,"second":1,"nano":0}
return LocalDateTime.of(joDate.get("year").getAsInt(),
joDate.get("month").getAsInt(),
joDate.get("day").getAsInt(),
joTime.get("hour").getAsInt(),
joTime.get("minute").getAsInt(),
joTime.get("second").getAsInt(),
joTime.get("nano").getAsInt());
}
}
Jdeps.deps 模块列表:
com.google.gson
java.base
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
org.slf4j
收到答案后,我打开了一个问题here。
TL;DR
您需要包含模块 jdk.unsupported.
的运行时映像(例如完整 JDK 或使用 jlink 构建的映像)
完整答案
GSON 想要创建 类 的实例,它在不调用任何构造函数的情况下反序列化(所以没有 GSON 这么说就没有初始化)。这通常无法完成,但 sun.misc.Unsafe
提供了一种使用方法 allocateInstance
来完成此操作的方法。为此,GSON 需要一个 sun.misc.Unsafe
的实例。调用堆栈中最顶层的帧来自 UnsafeAllocator
,它使用 common trickery to get Unsafe
.
问题是,sun.misc.Unsafe
在模块 jdk.unsupported 中,它以完整的 JDK 存在,但您通常不会在运行时图像中查找。
使用 jlink 创建运行时映像时,请确保包含选项 --add-modules jdk.unsupported
,这样您就可以开始了。
可以说,GSON 应该在 jdk.unsupported 上用 requires static
.
声明一个 optional dependency
我开发了一个存储来自设备的数据的小应用程序:我选择以 JSON 格式存储数据,数据的 serialization/deserialization 工作正常,即使它涉及一些我创建的自定义类型...但只有我在 IDE(就此而言 Eclipse)工作。
当我导出一个 运行nable JAR 文件时,数据的反序列化遇到了某种问题,因为软件总是抛出这个异常:
Caused by: java.lang.UnsupportedOperationException: Cannot allocate class LocalDateTime
at com.google.gson.internal.UnsafeAllocator.newInstance(UnsafeAllocator.java:104)
at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:225)
... 88 common frames omitted
我以为我会遇到自定义类型的问题,而不是内置类型。此时,我发现了两件事:
- 如果我对 运行 JAR 文件使用完整的 JRE 9,则不会抛出异常:我仔细检查了我用 Jlink.exe 创建的自定义 JRE 中包含的模块,一切都是正确包含。我仍然想使用较小的 JRE,所以我还没有进一步调查(我猜这解释了为什么在 IDE 它完美运行)
- 我向 Gson 对象添加了一个自定义反序列化器(见下文),我只是手动将 JSON 字符串转换为有效数据,这避免了 LocalDateTime class 上的异常...但异常再次出现在另一个 class 上,这次是定制的。
此时,我想我可以简单地为导致问题的每种数据类型添加一个反序列化器,但我想知道 为什么问题不会发生在完整的 JRE 上,以及为什么较小的 JRE 会导致此 ,即使包含所有必需的模块也是如此。也许还值得一提的是,我没有向保存数据的 Gson 对象添加自定义序列化程序,它全部按照 Gson 默认序列化。
LocalDateTime
解串器:
@Override
public LocalDateTime deserialize(JsonElement json, java.lang.reflect.Type type,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
JsonObject joDate = json.getAsJsonObject().get("date").getAsJsonObject();
JsonObject joTime = json.getAsJsonObject().get("time").getAsJsonObject();
//JSON example: {"date":{"year":2019,"month":1,"day":9},"time":{"hour":6,"minute":14,"second":1,"nano":0}
return LocalDateTime.of(joDate.get("year").getAsInt(),
joDate.get("month").getAsInt(),
joDate.get("day").getAsInt(),
joTime.get("hour").getAsInt(),
joTime.get("minute").getAsInt(),
joTime.get("second").getAsInt(),
joTime.get("nano").getAsInt());
}
}
Jdeps.deps 模块列表:
com.google.gson
java.base
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
org.slf4j
收到答案后,我打开了一个问题here。
TL;DR
您需要包含模块 jdk.unsupported.
的运行时映像(例如完整 JDK 或使用 jlink 构建的映像)完整答案
GSON 想要创建 类 的实例,它在不调用任何构造函数的情况下反序列化(所以没有 GSON 这么说就没有初始化)。这通常无法完成,但 sun.misc.Unsafe
提供了一种使用方法 allocateInstance
来完成此操作的方法。为此,GSON 需要一个 sun.misc.Unsafe
的实例。调用堆栈中最顶层的帧来自 UnsafeAllocator
,它使用 common trickery to get Unsafe
.
问题是,sun.misc.Unsafe
在模块 jdk.unsupported 中,它以完整的 JDK 存在,但您通常不会在运行时图像中查找。
使用 jlink 创建运行时映像时,请确保包含选项 --add-modules jdk.unsupported
,这样您就可以开始了。
可以说,GSON 应该在 jdk.unsupported 上用 requires static
.