Gson 的意外行为

il_*_*oga 3 json jlink gson java-9 jdeps

我开发了一个存储来自设备的数据的小应用程序:我选择以 Json 格式存储数据,数据的序列化/反序列化工作得很好,即使它涉及我创建的一些自定义类型......但只有我在IDE(Eclipse,就此而言)中工作。

但是当我导出一个可运行的 jar 时,数据的反序列化遇到了某种问题,因为软件总是抛出这个异常:

Caused by: java.lang.UnsupportedOperationException: Cannot allocate class LocalDateTime
    at com.google.gson.internal.UnsafeAllocator$4.newInstance(UnsafeAllocator.java:104)
    at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:225)
    ... 88 common frames omitted
Run Code Online (Sandbox Code Playgroud)

我以为我会遇到自定义类型的问题,而不是内置类型。此时,我发现了两件事:

  • 如果我使用完整的 JRE9 来运行 jar,则不会引发异常:我仔细检查了使用 Jlink.exe 创建的自定义 JRE 中包含的模块,并且所有内容都正确包含。我仍然想使用较小的 JRE,所以我还没有进一步调查(我想这解释了为什么在 IDE 中它可以完美运行)
  • 我向 GSON 对象添加了一个自定义反序列化器(见下文),我只是手动将 JSON 字符串转换为有效数据,从而避免了 LocalDateTime 类上的异常......但异常再次出现在另一个类上,这时间是定制的。

在这一点上,我想我可以简单地为导致问题的每种数据类型添加一个反序列化器,但我想知道为什么这个问题不会在完整的 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

Jdeps.deps 模块列表:

com.google.gson
java.base
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
org.slf4j
Run Code Online (Sandbox Code Playgroud)

编辑:在我收到答案后,我在这里打开了一个问题

Nic*_*lai 7

TL; 博士

您需要一个包含模块jdk.unsupported的运行时映像(例如完整的 JDK 或使用jlink构建的东西)。

完整答案

GSON 想要创建它反序列化的类的实例而不调用任何构造函数(所以没有 GSON 这么说,什么都不会被初始化)。这通常无法完成,但sun.misc.Unsafe提供了一种使用方法来做到这一点的方法allocateInstance。为此,GSON 需要一个sun.misc.Unsafe. 调用堆栈中最顶层的帧是 from UnsafeAllocator,它使用常见的技巧来获取Unsafe

问题是,sun.misc.Unsafe在模块jdk.unsupported 中,它存在于完整的 JDK 中,但通常不会在运行时映像中找到。

使用 jlink 创建运行时映像时,请确保包含该选项--add-modules jdk.unsupported,这样就可以了。

按理说,应该GSON声明一个可选的依赖jdk.unsupportedrequires static