java 9+模块系统默认强封装,反射访问需jvm启动时用--add-opens显式授权源模块/包=目标模块(如--add-opens java.base/java.lang=all-unnamed),该参数必须置于-cp或--module-path之前,不支持运行时动态添加或写入代码。

Java 9+ 的模块系统(JPMS)默认启用强封装,未显式导出或开放的包,外部模块连反射都访问不了。要临时打破这种限制,必须在 JVM 启动时通过命令行参数授权,不能运行时动态添加,也不能写在代码里。
用 --add-opens 开放反射权限(最常用)
当报 InaccessibleObjectException 或反射调用 setAccessible(true) 失败时,说明目标类所在包未被“打开”。需按格式指定模块、包和接收方:
-
格式:
--add-opens 源模块/包名=目标模块名 -
常见写法:
--add-opens java.base/java.lang=ALL-UNNAMED(解决 String、Class 等基础类反射问题) -
多包需重复写:如同时要用
java.util.ArrayList和java.io.File的私有字段,就得加两行:--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED - 接收方写 ALL-UNNAMED:代表你所有 classpath 下的代码(Spring Boot、普通 JAR 等)都算在内
用 --add-exports 导出包的可见性(仅限 public 成员)
当你用 Class.forName() 加载某个内部类(如 jdk.internal.ref.Cleaner),或第三方库直接 new 它的 public 类却报 IllegalAccessError,说明该类虽是 public,但所在包根本没被导出:
-
格式:
--add-exports 源模块/包名=目标模块名 -
示例:
--add-exports java.base/jdk.internal.ref=ALL-UNNAMED -
注意:它只让 public 类和成员“可见”,不赋予反射访问 private/protected 的能力——想反射还得配
--add-opens
用 --add-reads 增加模块可读性(较少见但关键)
某些模块默认不读取其他模块(哪怕你 requires 了),导致 ServiceLoader 找不到实现类,或模块间 Class.forName() 失败:
-
格式:
--add-reads 当前模块=被读取模块 -
示例:如果你的模块叫
my.app,但它需要读取java.xml才能加载 JAXB 实现,就加:--add-reads my.app=java.xml - 无 manifest 支持:这个参数不能写进 MANIFEST.MF,只能命令行传入
实际运行时的注意事项
这些参数不是“加了就行”,位置和组合很关键:
- 必须放在 -cp / -jar / --module-path 之前,否则 JVM 直接忽略
-
JDK 16+ 需配合 --illegal-access=permit(仅限 JDK 17 及以下),但更推荐统一用
--add-opens替代 - IDE 中配置位置:IntelliJ 在 Run Configuration → VM Options;Eclipse 在 Run Configurations → Arguments → VM arguments
-
Maven 构建时:在
maven-surefire-plugin的<jvmargs></jvmargs>里添加
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











