使用MethodHandles的单元测试本地正常,远程CI运行抛非法访问异常
跨模块反射访问异常的排查与解决方向
核心报错逻辑
这个异常不是代码逻辑写错了,本质是JDK模块系统的权限拦截:
- 代码里调用
MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup())时,要求Field所在的java.base模块,把对应的java.lang.reflect包开放给当前代码所属的模块,才允许获取到私有方法句柄。 - 从JDK16开始,JDK默认收紧了内部包的深度反射权限:
java.base下绝大多数内部包,默认不再对所有未命名模块开放深度访问能力,没有显式配置的情况下直接抛出IllegalAccessException,不再保留旧版本的警告提示。 - 对比JDK11的规则:JDK11默认允许未命名模块访问
java.lang.reflect这类包,哪怕不做特殊配置也能正常运行,这就是本地跑测完全没问题的核心原因。
本地正常、CI失败的差异来源
两边运行环境的权限规则完全不匹配:
- 本地环境:不管是IDE启动还是本地Maven执行测试,实际用的都是你预期配置的JDK11,匹配旧的模块开放规则,代码自然能正常跑通。
- CI环境:实际启动测试的JVM是OpenJDK16,严格执行新的模块权限规则,没有做适配配置的情况下直接触发权限拦截。
- 你当前Maven配置的Java11路径没有被实际加载,CI进程错误继承了节点上默认的OpenJDK16环境变量,才会出现配置版本和实际运行版本不一致的问题。
具体排查落地步骤
- 第一优先级先确认CI实际运行的JDK版本
- 别光看Maven的配置项猜版本,直接在测试用例最开头加日志打印,输出
System.getProperty("java.version")和System.getProperty("java.home"),拿到真实的运行环境信息,排除配置失效的问题。 - 检查CI节点的全局环境变量:确认系统默认的
JAVA_HOME是不是指向了OpenJDK16,Maven启动时有没有正确覆盖成你预期的JDK11路径;重点核对maven-surefire-plugin的配置,确认测试分支启动的JVM不是直接继承Maven父进程的环境。 - 如果CI是用容器镜像跑的,直接进入镜像查看内置的默认JDK版本,排除镜像版本和预期不匹配的问题。
- 别光看Maven的配置项猜版本,直接在测试用例最开头加日志打印,输出
- 确认版本后做对应适配
- 如果能修复环境问题,让CI实际跑在JDK11上:不需要改任何代码和配置,和本地运行逻辑保持一致即可。
- 如果CI确定要跑在JDK16+版本上:需要给测试用的JVM显式加上包开放参数,在
maven-surefire-plugin的配置中指定:<configuration> <argLine>--add-opens=java.base/java.lang.reflect=ALL-UNNAMED</argLine> </configuration>
- 避坑提醒
- 不要用已经废弃移除的
--illegal-access=permit参数,JDK16及以上版本已经完全移除这个开关,加了也不会生效。 - 不要为了图省事全量开放
java.base的所有包,只按需开放你实际反射访问的具体包即可,避免引入不必要的安全风险。
- 不要用已经废弃移除的
内容的提问来源于stack exchange,提问作者HighPredator
相关产品推荐
相关产品推荐

