DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Gson 读取 Instant 报 InaccessibleObjectException,检查版本与 JSON 格式

JDK17反射访问Instant失败时核对Gson版本;2.14默认时间对象格式与ISO字符串适配器分别处理,保留接口兼容。

JDK 17 上反序列化带 Instant 字段的 DTO,报 java.base does not "opens java.time" 或无法访问 Instant.seconds,先检查实际加载的 Gson 版本。旧版 Gson 可能尝试反射读取 JDK 类型的私有字段;Gson 2.14.0 已增加 java.time 内置适配器,处理方式与旧版不同。

还要检查接口约定的 JSON。2.14.0 的 Instant 默认表示采用包含 seconds、nanos 的对象,并不会自动改成 ISO 8601 字符串。解决反射访问与保持现有 JSON 契约,需要一起考虑。

即使输入是空对象,也可能在建立适配器时失败

旧版会为 DTO 声明的字段类型创建适配器,未必等到 JSON 真的出现该字段才处理。例如 Gson 2.8.8 与 JDK 17 的组合,输入 {} 仍可能因声明的 Instant 字段触发私有反射访问。

因此,删除请求中的时间字段、改成 JSON null 或换一个空对象,不是可靠修复。查看堆栈中的类型和实际 Gson 依赖,包括框架带入的版本、运行包和自定义适配器注册位置。

JDK 17 强封装后,旧的 --illegal-access 不能恢复这种访问。异常提到自己的模块包,与这里提到的 JDK java.time 包是不同场景,不应把所有包都开放。

能升级时,先核对默认格式是否相容

Gson 2.14.0 的内置 Instant 适配器通过公开 API 读写,不需要反射其私有字段。若系统原本采用对象形态的 JSON,可以按依赖兼容要求升级,并用已有真实输入和输出检查表示是否一致。

若接口原本约定时间字符串,则仍要明确注册对应适配器。不能把升级理解成“所有时间格式现在都能读取”,也不能把 Instant 的修复推广到其他任意第三方类型。

字符串契约使用明确的 Instant 适配器

下面适用于约定 ISO 8601 字符串或 JSON null 的接口。它应合入真正执行序列化的 Gson 实例,保留已有适配器和其他设置:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.time.Instant;
import java.time.format.DateTimeParseException;

Gson gson = new GsonBuilder()
    .registerTypeAdapter(Instant.class, new TypeAdapter<Instant>() {
        @Override
        public void write(JsonWriter out, Instant value) throws IOException {
            out.value(value.toString());
        }

        @Override
        public Instant read(JsonReader in) throws IOException {
            if (in.peek() != JsonToken.STRING) {
                throw new JsonParseException("Expected an Instant string");
            }
            try {
                return Instant.parse(in.nextString());
            } catch (DateTimeParseException e) {
                throw new JsonParseException("Invalid Instant string", e);
            }
        }
    }.nullSafe())
    .create();

这是初始化片段,需放在实际 Java 方法或初始化位置中。nullSafe() 处理 JSON null 与 Java null,Instant.parse 处理带明确时刻的字符串。日期只有年月日、无时区本地时间或历史对象格式,不属于这个适配器接受的输入;不要给 LocalDate、LocalDateTime 直接复用它。

新建另一个 Gson 后未交给实际调用方,不会改变框架或工具类原有实例。已经注册适配器却仍见反射堆栈时,优先确认报错路径使用的实例和声明类型,而不是继续添加 JVM 开放参数。

验收格式与错误路径

保留旧依赖和输出契约,检查真实时间值、纳秒精度、null、缺省字段及非法输入,并对照下游实际读取格式。若采用自定义字符串适配器,不能让原本消费对象表示的下游无声切换。

--add-opens 会扩大指定包的反射访问范围;它可以改变旧版行为,却不会定义稳定的 JSON 格式。优先采用合适的正式版本或明确适配器,移除不再需要的临时开放配置后再次验证。

升级后若异常指向其他类型,按新的字段与包重新检查。不要以一次 Instant 调用成功,认定所有模块反射问题都已消失。