Gson调用fromJson反序列化私有字段报无法访问错误如何解决
这个错误和Gson是否支持私有字段没有关系,本质是Java 9及以上版本引入的JPMS模块系统的反射访问限制导致的。
你贴的错误堆栈最底部的根因信息已经明确给出了触发原因:
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String com.example.server.Models.Game.name accessible: module com.example.server does not "opens com.example.server.Models" to module com.google.gson
Gson默认通过反射调用setAccessible(true)绕过访问修饰符限制,直接读写类的私有字段完成序列化、反序列化。Java 8及之前版本没有模块系统,反射可以随意访问私有成员,所以你查到的旧教程里直接用private字段都能正常运行。
Java 9之后的模块化规则默认禁止外部模块对未开放的包做深度反射访问,Gson所在模块拿不到你com.example.server.Models包的反射权限,自然无法访问私有字段。把字段改成public能正常运行,是因为public成员属于模块正常导出的可访问范围,不需要申请反射权限。
以下方案按推荐优先级排序:
方案1(最推荐,符合模块系统规范):在模块配置文件中开放对应包的反射权限
找到项目源码目录下的module-info.java文件,添加opens声明,把存放实体类的包开放给Gson模块:module com.example.server { // 保留你原有的requires、exports等配置 // 新增下面这行,开放Models包给Gson做反射访问 opens com.example.server.Models to com.google.gson; }如果项目中还有其他需要Gson序列化/反序列化的类所在的包,按相同格式追加
opens声明即可。这个方案不需要修改原有类的字段修饰符,也不需要写额外的适配代码,是模块化项目的标准解法。方案2(适用于非模块化传统项目):添加JVM启动参数开放反射权限
如果你没有写module-info.java,是靠传统classpath模式运行的项目,可以在程序启动的JVM参数中添加如下配置,给Gson开放对应包的访问权限:--add-opens com.example.server/com.example.server.Models=com.google.gson不推荐直接加全局开放权限的参数,会放大安全风险。
方案3(无反射,性能更好):为实体类编写自定义TypeAdapter
这种方案完全不依赖反射访问私有字段,手动实现JSON和实体类的转换逻辑,彻底绕开模块访问限制,示例代码如下:public class GameTypeAdapter extends TypeAdapter<Game> { @Override public void write(JsonWriter out, Game game) throws IOException { out.beginObject(); out.name("name").value(game.getName()); out.name("id").value(game.getId()); out.name("gameFields").jsonValue(toJson(game.getGameFields())); out.endObject(); } @Override public Game read(JsonReader in) throws IOException { Game game = new Game(); in.beginObject(); while (in.hasNext()) { String fieldName = in.nextName(); switch (fieldName) { case "name" -> game.setName(in.nextString()); case "id" -> game.setId(in.nextInt()); case "gameFields" -> game.setGameFields(fromJson(in, GameFields[].class)); // 不需要反序列化的players字段直接跳过即可 default -> in.skipValue(); } } in.endObject(); return game; } }写完后把适配器注册到Gson实例中即可正常使用:
Gson gson = new GsonBuilder() .registerTypeAdapter(Game.class, new GameTypeAdapter()) .create();这个方案的序列化性能比反射实现更好,缺点是每个实体类都需要写对应的转换逻辑,代码量更大。
不推荐方案:把所有类的字段改成public,或者强制给所有字段加public的getter/setter供Gson访问,这种写法会破坏类的封装性,没有必要。
内容的提问来源于stack exchange,提问作者Melfun

