答案是--add-opens JVM启动参数。它用于在JDK9+模块化环境下显式授权反射访问,格式为--add-opens=源模块/包=目标模块,如--add-opens=java.base/java.util=ALL-UNNAMED,须置于-jar或主类名之前生效。

这个问题实际指向的是 Java 模块系统(JPMS)下,序列化工具(如 Jackson、Protostuff、Gson)因无法反射访问内部字段而失败的典型场景。所谓“opens 指令”,并非独立命令,而是 JVM 启动参数 --add-opens,它是解决该问题最直接、最常用的实战手段。
明确报错来源:从异常中定位模块与包
当序列化工具触发 InaccessibleObjectException 或警告 Illegal reflective access 时,堆栈里一定包含类似这样的关键行:
Unable to make field java.lang.String.value accessible
从中可提取三要素:
-
目标类:如
java.lang.String -
所属模块:
java.lang.*类几乎全部归属java.base模块 -
目标包:取类全限定名去掉最后一段,即
java.lang(不是java.lang.*,也不是java)
正确书写 --add-opens 参数
格式固定为:--add-opens=模块名/包名=接收方模块名。常见组合如下:
-
--add-opens=java.base/java.lang=ALL-UNNAMED(应对 String、Class、Throwable 等核心类字段) -
--add-opens=java.base/java.util=ALL-UNNAMED(覆盖 ArrayList、HashMap、Optional 等容器内部状态) -
--add-opens=java.base/java.time=ALL-UNNAMED(修复 LocalDateTime、ZonedDateTime 的私有字段访问) -
--add-opens=java.base/java.io=ALL-UNNAMED(用于 File、FileDescriptor、BufferedInputStream 等)
注意:必须放在 -jar 或主类名之前,否则 JVM 不识别;ALL-UNNAMED 表示开放给所有传统类路径(classpath)下的代码,适用于绝大多数 Spring Boot、Tomcat、测试框架等场景。
避免盲目加参数:先诊断,再开权限
不是所有反射失败都靠加 --add-opens 解决。以下情况需另寻方案:
- 目标类来自已移除模块(如
javax.xml.bind.DatatypeConverter属于已废弃的java.xml.bind),应改用jakarta.xml.bind替代,而非强行 opens - 序列化库本身支持无反射模式(如 Jackson 的
@JsonCreator+ 构造器、Gson 的FieldNamingPolicy),优先启用这些机制 - 自定义类字段被
private final修饰且无 setter,JVM 封装限制只是表象,根源是设计上未预留序列化契约——应补充public构造器或toMap()方法
IDE 与构建工具中的配置要点
开发阶段常忽略运行环境与 IDE/构建工具的 JVM 参数隔离:
- IntelliJ IDEA:Run → Edit Configurations → VM Options 中填写,勿写在 Program Arguments
-
Maven Surefire(测试):
<jvmArgs>--add-opens=java.base/java.time=ALL-UNNAMED</jvmArgs> -
Gradle(JavaExec):
jvmArgs = ['--add-opens=java.base/java.util=ALL-UNNAMED'] -
Spring Boot fat jar:启动脚本中,在
java -jar后、jar 路径前插入参数


















