5.4 插件版本兼容:老插件遇上新 JDK 本节摘要:插件版本兼容问题的主要形态是旧版本插件在新 JDK 上运行失败、跨大版本的 API 变更、以及未锁定版本的静默漂移。处方包括用 compiler 插件的 release 参数声明目标字节码、全量锁定插件版本、用 enforcer 的版本规则做门禁。本节复盘一次 JDK 17 升级的插件罢工潮。 JDK 17 升级当晚的连环车祸 risk-engine 计划从 JDK 8 升到 17。代码层的修改做完后,构建当晚连环报错: 两起故障、两种根因。第一起:surefire 2.12.4 是 2012 年的版本,对新 JDK 的类库与字节码一无所知——它怎么会是这个版本?POM 里没写版本,它来自一张古老的默认绑定表(第 2.
本节摘要:插件版本兼容问题的主要形态是旧版本插件在新 JDK 上运行失败、跨大版本的 API 变更、以及未锁定版本的静默漂移。处方包括用 compiler 插件的 release 参数声明目标字节码、全量锁定插件版本、用 enforcer 的版本规则做门禁。本节复盘一次 JDK 17 升级的插件罢工潮。
risk-engine 计划从 JDK 8 升到 17。代码层的修改做完后,构建当晚连环报错:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test on project risk-web: 当前的系统配置不支持该版本 surefire [ERROR] Failed to execute goal ... 自研发布插件 1.2.0:publish on project risk-web: java.lang.reflect.InaccessibleObjectException 无法令某个内部对象可访问 模块系统封锁
两起故障、两种根因。第一起:surefire 2.12.4 是 2012 年的版本,对新 JDK 的类库与字节码一无所知——它怎么会是这个版本?POM 里没写版本,它来自一张古老的默认绑定表(第 2.2 节讲过 packaging 的默认绑定),老 Maven 版本默认绑定就停在那个年代。第二起:自研发布插件用了反射访问 JDK 内部 API,JDK 17 的模块系统(强封装)把内部 API 上了锁,InaccessibleObjectException 是封锁的直接回执。
第一步,全量盘点插件版本。 用有效 POM(第 1.4 节工具)把所有插件的"实际生效版本"列出来,标出三类:没写版本的(继承默认绑定,风险最高)、版本明显过时的、自研的。
第二步,逐个升级并锁定。 surefire 升到 3.x 系(对新 JDK 的支持完整),所有插件在父 POM 的 pluginManagement 统一钉版本:
<!-- 父 POM:插件版本的中央仲裁 与依赖仲裁同构 --> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <!-- release 参数:声明目标字节码版本 优于旧的 source 加 target 组合 --> <release>17</release> </configuration> </plugin> </plugins> </pluginManagement> </build>
compiler 插件的 release 参数值得专门说清:旧式写法是 source 加 target 各配一个版本,它不约束引导类路径,交叉编译时可能引用了新 JDK 的 API 而编译器不报错,运行到旧 JVM 才炸;release 参数一次声明"按哪个平台的 API 面编译",把这类事故挡在编译期。
第三步,处置强封装冲突。 自研插件的反射访问需要逐处改造:优先改用公开 API(多数内部 API 有公开替代);确无替代的,用 add-opens 参数按需开孔(在 argLine 与 MAVEN_OPTS 里精确声明要开的模块与包),并给每处开孔写注释说明理由——开孔清单本身就是技术债台账,注释丢失的 add-opens 是下一代人的迷宫。
故障修完,制度跟上两条。门禁一:requirePluginVersions,强制所有插件显式声明版本,没写直接构建失败:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <executions> <execution> <id>enforce-plugin-versions</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <!-- 插件版本必须显式声明 杜绝默认绑定表的年代穿越 --> <requirePluginVersions/> </rules> <fail>true</fail> </configuration> </execution> </executions> </plugin>
门禁二:升级演练流水线。 大版本升级(JDK、Spring 大版本)前,先在独立分支跑"全模块全插件构建加全量测试",把兼容问题圈在演练环境。插件兼容矩阵(JDK 版本乘插件版本)不必人脑记忆,演练流水线就是矩阵的执行者。
⚠️ 常见坑:以为"插件没配版本 = 用最新版"。实际语义是"用默认绑定表里的版本",而绑定表跟的是 Maven 版本与 packaging 类型——一套老旧的绑定表能让你的构建用着十年前的 surefire 而毫无提示。这比"用了最新版不稳定"更隐蔽,因为它从不报警。
💡 关键直觉:依赖的版本要仲裁(第 3.3 节),插件的版本同样要仲裁,且两者的建制一模一样:pluginManagement 对 dependencyManagement,enforcer 版本门禁对收敛门禁。把两套纪律在脑子里对齐,多模块工程的配置治理就有了完整的对称结构。
故障与门禁都齐了。下一节立最后一条制度线:可复现构建——让同一个 commit 在任何时间、任何机器上产出一致的结果。