Minecraft Forge开发中gson.toJson序列化对象报模块访问异常
根本原因
异常由两个核心因素共同触发:
- 高版本Minecraft Forge(1.17+)运行在Java 17及以上版本,Java 9引入的模块系统(JPMS)默认禁止外部模块通过反射访问JDK核心包(比如
java.util)下类的私有字段,而Gson默认的序列化策略是通过反射递归遍历对象的所有字段(包括私有、继承的内部字段)完成序列化。 MobEffectInstance是Minecraft原生类,其内部继承链、关联字段中包含JDKjava.util包下的类实例(异常中明确提到的java.util.Random就是其中之一)。当Gson尝试反射读取这些JDK类的私有字段时,就会触发模块访问权限报错。- 测试用的
ArrayList<Integer>、拆解后的HashMap<String, Integer>不会触发异常,是因为Integer、String等基础/包装类型有Gson内置的专属序列化器,不需要走反射遍历内部字段的逻辑,自然不会触碰JDK模块的访问限制。
规范解决方案
按推荐优先级从高到低排列:
方案1:自定义独立数据模型(最推荐)
不要直接持有Minecraft原生类的引用,专门定义只包含业务所需字段的普通类存储药水效果数据,从根源上避免Gson递归反射Minecraft、JDK内部类字段。
示例代码:
// 自定义药水效果数据类,仅保留需要的公开基础类型字段 public class PotionEffectData { public String effectId; public int duration; public int amplifier; public boolean isAmbient; public PotionEffectData(MobEffectInstance instance) { this.effectId = instance.getEffect().getDescriptionId(); this.duration = instance.getDuration(); this.amplifier = instance.getAmplifier(); this.isAmbient = instance.isAmbient(); } } // 修改MyInfo类的对应字段 public class MyInfo { public int ID; public float Health; public double X; public double Y; public double Z; public ArrayList<PotionEffectData> ActivePotionEffects; public MyInfo(Player play) { ID = play.getId(); Health = play.getHealth(); X = play.getX(); Y = play.getY(); Z = play.getZ(); ActivePotionEffects = new ArrayList<>(); for (MobEffectInstance effect : play.getActiveEffects()) { ActivePotionEffects.add(new PotionEffectData(effect)); } } }
这种方案结构清晰、序列化逻辑可控,没有兼容性问题,是生产环境的首选实现。
方案2:注册Gson自定义类型适配器
如果不想新增额外的数据类,可以给Gson注册MobEffectInstance的专属序列化器,明确告诉Gson序列化该类时不需要走反射,直接按指定逻辑提取字段生成JSON,完全绕开反射遍历内部字段的逻辑。
示例代码:
// 定义MobEffectInstance的序列化器 public class MobEffectInstanceSerializer implements JsonSerializer<MobEffectInstance> { @Override public JsonElement serialize(MobEffectInstance src, Type typeOfSrc, JsonSerializationContext context) { JsonObject effectObj = new JsonObject(); effectObj.addProperty("effectId", src.getEffect().getDescriptionId()); effectObj.addProperty("duration", src.getDuration()); effectObj.addProperty("amplifier", src.getAmplifier()); effectObj.addProperty("isAmbient", src.isAmbient()); // 可按需增删需要序列化的字段 return effectObj; } } // 构建Gson实例时注册适配器 Gson _Json = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(MobEffectInstance.class, new MobEffectInstanceSerializer()) .create();
该方案不需要修改原有MyInfo类的字段定义,仅通过Gson配置解决问题,也是Gson官方推荐的第三方类序列化处理方式。
方案3:添加JVM启动参数(不推荐)
可以通过添加JVM启动参数强制开放JDK包的反射权限绕过模块检查,参数如下:
--add-opens java.base/java.util=com.google.gson
该方案治标不治本,如果后续序列化的类中引用了其他JDK包的私有字段,还需要追加更多--add-opens配置,且模组分发后需要用户手动修改启动参数,兼容性极差,仅适合本地调试使用。
内容的提问来源于stack exchange,提问作者YunoFreecs
相关产品推荐
相关产品推荐

