6.3 Maven 与 Kotlin:多语言协同构建 本节摘要:Kotlin 与 Java 混编工程的关键配置是 kotlin-maven-plugin 的编译顺序(先 Kotlin 后 Java),版本纪律要求编译器与标准库对齐(由 kotlin-bom 仲裁),增量编译开关与源码目录声明决定构建体验。本节给出可直接套用的双模块配置与三类高频翻车点的处置。 为什么规则引擎改用 Kotlin 写 risk-engine 的规则解析模块重写选了 Kotlin:密封类与模式匹配让规则分支的表达力上一个台阶,空安全把一类运行期异常变成编译期报错。但工程现实是:其他模块仍是 Java,团队不可能一夜切换。于是问题变成:同一个 Maven 工程里,两种语言怎么共存共编?
本节摘要:Kotlin 与 Java 混编工程的关键配置是 kotlin-maven-plugin 的编译顺序(先 Kotlin 后 Java),版本纪律要求编译器与标准库对齐(由 kotlin-bom 仲裁),增量编译开关与源码目录声明决定构建体验。本节给出可直接套用的双模块配置与三类高频翻车点的处置。
risk-engine 的规则解析模块重写选了 Kotlin:密封类与模式匹配让规则分支的表达力上一个台阶,空安全把一类运行期异常变成编译期报错。但工程现实是:其他模块仍是 Java,团队不可能一夜切换。于是问题变成:同一个 Maven 工程里,两种语言怎么共存共编?
混编的核心矛盾只有一个:编译顺序。Java 编译器不认识 Kotlin 源文件,Kotlin 编译器却能先编译自己并把产物给 Java 用。所以标准编排在 Maven 里是"先 Kotlin 后 Java"——kotlin 插件绑到更早的阶段,Java 编译器随后把 Kotlin 的产物当普通 class 文件引用。
<build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <!-- 编排核心:Kotlin 编译挂到 compile 与 test-compile 的前面 --> <execution> <id>compile</id> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <goals> <goal>test-compile</goal> </goals> </execution> </executions> <configuration> <!-- 增量编译:开发期反馈速度的关键开关 --> <args> <arg>-Xincremental</arg> </args> </configuration> </plugin> </plugins> </build>
依赖侧只需标准库,版本交给 BOM:
<dependencyManagement> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-bom</artifactId> <version>1.9.10</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <!-- 版本由 BOM 裁决 不写字面量 --> </dependency> </dependencies>
翻车一:编译顺序配错。 症状是 Java 代码引用 Kotlin 类时编译器报"找不到符号"。诊断看构建日志里两个编译器的执行顺序——kotlin 的 compile goal 必须出现在 maven-compiler-plugin 之前。多数成因是 executions 的编排被后来者覆盖,或从别的项目抄配置时丢了 execution。处置武器仍是老三样:有效 POM 看最终编排,日志看实际顺序,最小工程复现验证。
翻车二:编译器与标准库版本错位。 kotlin-stdlib 1.8 的 API 配 1.6 的编译器,编译期报元数据版本不兼容;反过来用新编译器编旧库,警告刷屏。版本对齐纪律:kotlin.version 属性(管插件即编译器)与 kotlin-bom 版本(管标准库与协程等家族)必须同步走,升级时一起动。这条纪律与第 3.3 节"版本只有一个家"一脉相承,只是 Kotlin 的坑在于版本散落在两处,务必收敛到一个属性。
翻车三:协程依赖的传递面失控。 Kotlin 工程引入协程库后,传递树里多出几级 Kotlin 家族构件,与 Java 侧引入的其他 Kotlin 系库(例如某客户端库自带 Kotlin 支持)相遇时,版本调解照第 3 章规则执行。处置是熟悉的配方:平台 BOM 钉住 Kotlin 家族版本,verbose 树核对调解结果。多语言没有发明新的依赖规则,只是把战场从 Java 族扩大到了 Kotlin 族。
| 模式 | 结构 | 适用 | 代价 |
|---|---|---|---|
| 单模块混编 | 一个模块里两种源码目录 | 试点期 小规模渗透 | 配置耦合 测试覆盖复杂 |
| 分模块分语言 | rule 模块纯 Kotlin web 模块纯 Java | 稳定演进 | 模块边界要清晰 |
risk-engine 的演进路径是先单模块试点(一个服务里两页 Kotlin 文件验证表达力收益),三个月后拆出独立的 Kotlin 模块。建议超过两成的代码是 Kotlin 时就走分模块——单模块混编的编译编排、测试配置、IDE 行为都要同时伺候两种语言,分模块让每种语言待在自己舒适的配置里,模块边界(第 4.1 节的原则)天然就是语言边界。
⚠️ 常见坑:只加 kotlin 插件不加标准库依赖(或反之)。插件管编译,标准库管运行期符号,两者缺一:前者让构建当场失败,后者让运行期才报类找不到。配置检查清单两项必须成对出现。
💡 关键直觉:判断一个混编工程配置是否健康的速查法——看 kotlin.version 这个属性在全部 POM 里出现了几处。一处(父 POM 的 properties)是健康,两处以上就开始有错位风险,散落各处则是第 3 章丛林裁决的多语言复刻。
语言协同就绪。下一节进入交付形态的性能极点:GraalVM 原生镜像——把 JVM 世界编译成原生二进制的完整战役与排障路径。