JDK 17 升级后单测集体报 InaccessibleObjectException:--add-opens 该怎么加才对
一、症状非常统一
项目从 JDK 8/11 升到 17,编译打包都没问题,一跑单测就成片报同一句:
java.lang.reflect.InaccessibleObjectException: |
出错的不只是自研代码,而是 Mockito、ByteBuddy、CGLIB、Jackson、Hibernate 这些运行时靠深层反射干活的库。它们要 setAccessible(true) 打开 java.base 里的内部包,而 JDK 17 的强封装(JPMS)默认拒绝未授权的深度反射。
二、先排除三个无效操作
| 操作 | 结论 |
|---|---|
加 --illegal-access=permit |
无效。该选项在 JDK 16 起已失效、17 中不再起作用 |
| 只在 IDE 的 Run Configuration 里加参数 | 只救本地,mvn test 和流水线仍然红 |
只在 maven-compiler-plugin 配参数 |
这是运行时参数,编译期加了没有意义 |
关键认识:--add-opens 是给 JVM 启动用的,而 Surefire 会 fork 出一个独立的测试 JVM。参数必须通过 argLine 传进那个子进程,加在主进程上不算。
三、正确写法
<plugin> |
四个容易写错的地方:
- **格式是
模块/包=目标模块**,不是java.base=ALL-UNNAMED。目标写成ALL-UNNAMED是因为测试类通常由未命名模块的类加载器加载。 --add-opens只到包一级,不能精确到类,也无法递归子包,用到哪个开哪个。- Surefire 版本要 ≥ 3.0.0。2.22.x 时代对模块参数的处理不一致,常见现象是参数加上了但被静默忽略,让人以为”改了没用”。
- 集成测试要同步配。
maven-failsafe-plugin是另一个 fork 进程,只改 Surefire 的话,mvn verify阶段照样挂。
写成属性形式更利于多模块复用:
<properties> |
四、比加参数更好的两条路
--add-opens 本质是给旧实现打临时通道,不是解决问题。按代价从低到高:
- 优先升级依赖:Mockito 5.x、ByteBuddy 1.14+、Jackson 2.15+ 都已适配 JDK 17 的反射约束。我们手上几个老项目实测下来,一多半的
InaccessibleObjectException在升级依赖后直接消失,根本不需要开包。 - 收敛反射用法:把”用反射注入私有字段”的测试改成走构造器/Setters 或提供测试可见的包级方法。这类改动比看上去少——很多反射只是历史习惯。
- 长期方案:如果项目本身模块化了,可以在
module-info.java里显式opens ... to,把开放范围限定到具体模块而不是ALL-UNNAMED。
不要做的事:把 --add-opens 抄成一份二十行的”全家桶”清单贴进 CI。范围越大,将来升到下一个 LTS 时的隐性风险越高,而且违反格式(例如把模块名写成 ALL-UNNAMED)会让 JVM 直接启动失败,测试从”报错”变成”跑不起来”。

五、洹拓科技的升级服务
新乡及豫北不少企业还在跑 JDK 8,出于安全和性能考虑近两年陆续要动。我们的经验是把这件事拆成三步,而不是一次梭哈:
- 依赖体检:列出所有依赖反射的库及其最低兼容版本,先升级再谈改造
- 流水线并行:新旧 JDK 各跑一份构建,用测试结果差异来判断改造是否到位
- 灰度切换:先切测试环境,再切预生产,生产窗口内保留回退包
需要评估存量项目升级成本的,可以找洹拓科技做一次JDK 升级可行性评估,输出一份含依赖清单、风险项和工作量的报告。
要点已与 JDK 17 强封装机制及 Maven Surefire 参数传递行为核对。
新乡洹拓科技有限公司,软件开发 · AI模型研发 · 云服务与部署运维。
咨询热线:400-829-9957
地址:河南省新乡市辉县市金城百货四楼