最合规方式是在模块声明中用opens显式授权反射访问:仅开放测试真正依赖的包(如opens com.example.app.service;),避免open拼写错误或opens to all,JUnit等仍可正常使用setAccessible(true)。
直接在模块声明中用 opens 开放对应包,是让单元测试工具(如 junit、testng)能通过反射访问私有字段或方法的最合规方式。java 9+ 模块系统默认禁止这种访问,而 opens 不是“绕过安全”,而是显式授权,符合 jpms 设计本意。
明确哪些包需要 opens
不是整个模块都 open,只开放测试真正依赖反射的包:
- 若测试类要读取
MyService的私有cacheSize字段,且该类在com.example.app.service包下,则需opens com.example.app.service - 若测试使用 Jackson 反序列化私有构造器或字段,且模型类在
com.example.app.model,就 opens 那个 model 包 - 避免
opens to all写法(如opens com.example.app to java.base),这会破坏封装边界,仅在极特殊调试场景才考虑
module-info.java 中正确声明 opens
在模块根目录的 module-info.java 里添加行,注意顺序和语法:
-
opens com.example.app.service;—— 允许所有模块(包括未命名模块,即测试类所在环境)通过反射访问 -
opens com.example.app.model to com.example.app.test;—— 更严格:只允许指定测试模块访问(需测试项目也声明为命名模块) - 不能写成
open com.example...(少 s)或放在exports前面(语法错误)
测试代码保持原样,无需改写逻辑
JUnit 测试仍可使用 Field.setAccessible(true),只要模块已 opens,就不会抛出 “Unable to make field … accessible” 错误:
- 例如反射获取
File.path的旧测试代码,在 JDK 17+ 下只需在java.base模块被 opens 的前提下运行(通常通过 JVM 启动参数--add-opens java.base/java.io=ALL-UNNAMED补充,见下条) - 第三方库(如 Mockito、PowerMock)内部反射调用也能正常工作,前提是其目标包已被你的模块或 JVM 参数显式 opens
开发阶段补充 JVM 参数(临时但实用)
当无法修改被测模块的 module-info.java(如测试第三方库或 legacy 模块),可在运行测试时加参数:
-
--add-opens java.base/java.io=ALL-UNNAMED—— 解决File.path类报错 -
--add-opens java.base/java.lang=ALL-UNNAMED—— 常用于反射访问String.value等 - IDE 中配置在 Run Configuration → VM Options;Gradle 中加在
test { jvmArgs = [...] }


















