第4章 Kotlin项目构建:Gradle配置、Kotlin编译与依赖管理

做Kotlin项目,绕不开Gradle。说句实话,我见过不少团队在Gradle配置上栽跟头——不是依赖冲突半天解不掉,就是编译缓存在CI上不生效。我自己刚到新公司接手第一个Kotlin微服务时,就被一个诡异的“编译通过但运行时类找不到”的问题折腾了一整天,后来发现是构建脚本里漏了runtimeOnly配置。嗯,这节课咱们就把Gradle配置、Kotlin编译和依赖管理这三大块彻底讲透。

为什么是Gradle?

现在Kotlin项目的官方构建工具就是Gradle,尤其是Kotlin DSL(.kts结尾)已经成为事实标准。你想想看,既然整个项目都用Kotlin写了,构建脚本还用Groovy?那多别扭。我个人习惯全程用build.gradle.kts,IDE提示更友好,类型检查也更严格。

对比项 Groovy DSL Kotlin DSL
文件后缀 .gradle .gradle.kts
类型安全 弱类型,容易写错 强类型,IDE自动补全
学习成本 需要额外学Groovy 熟悉Kotlin即可
性能 稍快(但差距不大) 编译脚本本身需编译

建议所有新项目直接上Kotlin DSL。老项目迁移?也别怕,Gradle官方提供了迁移指南,我迁移过两个中型项目,大概花半天就能搞定。

Gradle核心配置:从零搭建一个Kotlin项目

一个标准Kotlin项目的build.gradle.kts长这样:

plugins {
    kotlin("jvm") version "1.9.22"
    application
}

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
    testImplementation(kotlin("test"))
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

application {
    mainClass.set("com.example.MainKt")
}

tasks.test {
    useJUnitPlatform()
}

注意几点:

  • 插件声明:kotlin("jvm") 是你唯一需要的Kotlin插件,它会自动配置编译任务。
  • 仓库:mavenCentral() 是主力仓库,如果你用公司私有仓库,加 maven { url = uri("https://nexus.example.com/repository/maven-public/") }。
  • 依赖声明:implementation 表示编译和运行时都需要,testImplementation 只在测试阶段生效。
  • 测试框架:JUnit 5 是目前主流,useJUnitPlatform() 这行容易忘,我踩过这个坑。

Kotlin编译配置:JVM版本与编译器选项

很多时候你需要在CI里锁定JVM目标版本,避免开发环境和生产环境不一致。比如服务器JDK是17,但本地是21——那编译出来的字节码版本必须是17才安全。

kotlin {
    jvmToolchain(17)

    compilerOptions {
        languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
        apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
        freeCompilerArgs.add("-Xjvm-default=all")
    }
}

我习惯用 jvmToolchain 来统一JDK版本,它自动下载匹配的JDK,CI镜像里甚至不用装JDK 17。至于 -Xjvm-default=all,这是为了让接口中的默认方法在JVM上正确生成。曾经有个同事没加这个参数,结果接口的默认实现在Java端调用时抛AbstractMethodError……你们猜修了多久?

💡 小技巧: 在Gradle 8+ 中,jvmToolchain 需要先声明工具链仓库:toolchainManagement { jvm { javaRepositories { repository("mavenCentral") } } }

依赖管理:版本统一与冲突解决

依赖管理是构建配置里最头疼的部分。我经手过一个项目,dependencies 块里直接写死了十几个版本号,升级一次要改半天。后来用 version catalog 统一管理:

// gradle/libs.versions.toml
[versions]
kotlin = "1.9.22"
junit = "5.10.0"
coroutines = "1.7.3"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }

然后在build.gradle.kts里引用:

dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(libs.coroutines.core)
    testImplementation(libs.junit.jupiter)
}

这样版本号集中在一个地方,Dependabot自动更新也方便。如果你遇到依赖冲突(比如两个库依赖了不同版本的同个库),用 ./gradlew :app:dependencies 查看依赖树,然后可以用 force 或升级其中一个库来解决。我建议优先升级,别偷懒用force。

多模块项目配置要点

在CI流水线中,多模块项目需要额外注意子模块间的依赖顺序。Gradle会自动处理,但如果你用了api和implementation的区别混淆,会出现编译没问题但运行时类找不到的情况。记住一条原则:

  • implementation:依赖只对当前模块可见,对外不暴露。
  • api:依赖同时暴露给外部消费者(相当于旧的compile)。

比如你的 core 模块依赖 kafka-clients,但只有 core 内部使用,那就用 implementation。如果需要暴露给其他模块,才用 api。这样做能减少编译时传递依赖的规模,加快构建速度。

SVG:Kotlin项目构建流程

Kotlin项目构建与Gradle配置核心流程 Kotlin 源代码 build.gradle.kts Gradle 构建配置 远程 / 本地仓库 Kotlin 编译器 (kotlin.jvm 插件) JVM 字节码 (class / jar) 单元 / 集成测试 (JUnit 5) ⚠️ 依赖管理核心:version catalog + 统一仓库,避免硬编码版本号

CI集成时需要特别留意的几个点

刚才讲的是本地构建,放到CI流水线里,有几点不一样:

  1. Gradle Wrapper 必须上传到仓库。CI环境不需要预装Gradle,用 ./gradlew 就行。我见过有人把.gradle目录也提交了,那是错的,会引发缓存冲突。
  2. 配置缓存:Gradle 8支持配置缓存,显著加速CI。在gradle.properties里开启 org.gradle.configuration-cache=true。但是注意,如果你的构建脚本里用了System.getenv()之类的不兼容API,会报错。需要做一次适配。
  3. 依赖缓存:CI Runner最好挂载持久化卷保存~/.gradle/caches,避免每次重新下载所有依赖。我自己管理的CI流水线,依赖缓存命中率在85%以上,每次构建节省2~3分钟。
🚀 经验之谈: 在GitLab CI中,可以用 cache 关键字来缓存.gradle目录。但注意别把构建产物也混进去——只缓存 ~/.gradle/caches 和 ~/.gradle/wrapper 就够了。

实战:一个典型的build.gradle.kts完整示例

我把上面讲的内容整合成一个相对完整的配置,你可以直接拿去用:

plugins {
    kotlin("jvm") version "1.9.22"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
    application
}

group = "com.example"
version = "1.0.0"

repositories {
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
    implementation("io.ktor:ktor-client-core:2.3.6")
    testImplementation(kotlin("test"))
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
    jvmToolchain(17)
    compilerOptions {
        freeCompilerArgs.add("-Xjvm-default=all")
    }
}

application {
    mainClass.set("com.example.AppKt")
}

tasks.test {
    useJUnitPlatform()
    testLogging {
        events("passed", "skipped", "failed")
    }
}

说个避坑指南:testRuntimeOnly 有时候会被漏掉,导致JUnit Platform不工作。我去年有一次就是因为这个,测试任务一直在报“No tests found”,折腾了半小时才发现是少了这一行。

总结表格:本章知识速查

主题 关键点 常见错误
Gradle DSL 使用Kotlin DSL,类型安全 混用Groovy和Kotlin语法
Kotlin编译器 设置jvmToolchain + compilerOptions 忘记指定JVM版本导致编译产物不兼容
依赖管理 version catalog统一版本 写死版本号,升级困难
CI缓存 缓存~/.gradle/caches/wrapper 提交.gradle目录到git
测试配置 useJUnitPlatform() + testLogging 漏掉junit-platform-launcher