运行JUnit时Gson序列化Optional抛InaccessibleObjectException
异常产生原理
该异常是Java 9及以上版本引入的Java平台模块化系统(JPMS)强封装机制,结合旧版本Gson的序列化逻辑共同触发的:
- 触发点就是日志行里的
gson.toJson(roleDTOEmployee)调用:你传入的参数是Optional<RolesDTO>类型,2.8.6之前的老版本Gson没有为Optional类型实现专属序列化适配器,会默认走反射序列化逻辑。 - Gson的反射序列化逻辑会调用
setAccessible(true),尝试暴力访问Optional类的私有final字段value,拿到Optional内部存储的实际对象做序列化。 - Java 9+的模块化规则明确要求:核心模块
java.base默认不会向类路径上的未命名模块(也就是你运行JUnit测试时业务代码所在的加载模块)开放java.util包的深度反射访问权限,检测到非法反射访问时就会直接抛出InaccessibleObjectException。 - 删除日志代码后,不会触发Gson序列化Optional实例的逻辑,自然不会触发非法反射访问,因此测试可以正常运行。
无需删除日志的解决方案
按推荐优先级排序:
- 优先方案:升级Gson版本
将项目使用的Gson依赖升级到2.8.6及以上正式版本,新版本已经内置了Optional、OptionalLong等JDK8新增类型的专属序列化适配器,序列化时不会再走反射访问私有字段的逻辑,从根源上规避模块访问限制,原有业务代码不需要做任何修改即可正常运行。 - 兼容方案:序列化前手动解包Optional
不直接将Optional实例传入Gson序列化,先手动取出Optional内部存储的实际值再做序列化,修改日志代码如下即可:
这个方案不需要调整依赖、不需要加JVM参数,完全绕开了Gson对Optional类的反射操作,在各个JDK版本下都能稳定运行。if (logger.isInfoEnabled()) { // 用orElse取出内部值,空Optional时序列化为null logger.info("roleDTOEmployee {}", gson.toJson(roleDTOEmployee.orElse(null))); } - 临时方案:添加JVM启动参数放开反射限制
如果暂时无法升级Gson、也不便于调整业务代码,可以在JUnit的运行配置中添加如下JVM启动参数,显式放开对应包的反射访问权限:
该参数会告知JVM允许所有未命名模块的代码通过反射访问--add-opens java.base/java.util=ALL-UNNAMEDjava.util包下类的所有成员,直接绕过模块化的访问检查。注意这属于临时绕过方案,不推荐在生产环境长期使用。
内容的提问来源于stack exchange,提问作者Harsh
相关产品推荐
相关产品推荐

