9、依赖管理:仓库配置与依赖声明

依赖管理,说白了就是 Gradle 帮你自动下载和管理第三方库。我刚开始用 Gradle 时,觉得这玩意儿不就是个「自动下载器」嘛?后来踩过几次坑才发现,这里面的门道还真不少。

今天咱们就聊聊仓库配置、依赖声明,还有依赖传递与排除。嗯,这些都是日常构建中绕不开的核心技能。

9.1 仓库配置:你的代码从哪里来?

Gradle 需要知道去哪里下载依赖。这个「去哪里」就是仓库配置。我个人习惯把仓库配置写在 repositories 块里,像这样:

repositories {
    mavenCentral()
    jcenter()
    maven { url "https://maven.aliyun.com/repository/public" }
    mavenLocal()
}

这里有几个常见的仓库:

仓库名称 说明 现状
mavenCentral() Maven 中央仓库,最权威 推荐使用,稳定可靠
jcenter() JFrog 维护的仓库 已停止更新,不建议新项目使用
mavenLocal() 本地 Maven 缓存 适合调试本地构建
私服(自定义) 企业内部仓库 企业项目必备
注意: jcenter 已经进入只读模式了。我记得 2021 年有个项目还在用 jcenter,结果新依赖死活拉不下来。后来全部迁移到了 mavenCentral。所以新项目直接上 mavenCentral 就好。

私服配置稍微复杂一点,需要指定 URL 和认证信息:

repositories {
    maven {
        url "http://nexus.company.com/repository/maven-public/"
        credentials {
            username = "admin"
            password = "password123"
        }
    }
}

我曾经在配置私服时忘记加认证信息,结果构建一直报 401 错误。排查了半天才发现是 credentials 没写对。嗯,这种低级错误大家别犯。

9.2 依赖声明:implementation vs api vs compileOnly

依赖声明是 Gradle 依赖管理的核心。你想想看,一个依赖到底该用哪种方式声明?这直接影响到编译速度和依赖传递。

先看个例子:

dependencies {
    implementation 'com.google.guava:guava:31.1-jre'
    api 'org.springframework:spring-core:5.3.20'
    compileOnly 'org.projectlombok:lombok:1.18.24'
    runtimeOnly 'mysql:mysql-connector-java:8.0.30'
    testImplementation 'junit:junit:4.13.2'
}

这里我重点说说 implementationapi 的区别:

声明方式 对外暴露 编译速度 使用场景
implementation 不暴露 内部实现依赖
api 暴露给消费者 公共 API 依赖
compileOnly 仅编译期 编译时需要,运行时不需要
runtimeOnly 仅运行时 运行时需要,编译时不需要
核心原则:能用 implementation 就别用 api。我见过太多项目把所有依赖都写成 api,结果编译慢得要命。其实大部分依赖都是内部实现细节,没必要暴露出去。

举个例子,假设你写了一个工具库:

// 你的工具库
dependencies {
    implementation 'com.google.gson:gson:2.9.0'  // 内部使用 Gson
    api 'org.slf4j:slf4j-api:1.7.36'            // 用户需要用到 SLF4J
}

这里 Gson 是你内部用的,用户不需要知道。而 SLF4J 是用户需要直接调用的,所以用 api 暴露出去。

9.3 依赖传递与排除

依赖传递是 Gradle 自动帮你做的事情。你引入一个库,它会把该库依赖的其他库也一起拉下来。听起来很方便对吧?但有时候也会带来麻烦。

我曾经遇到过一个经典问题:项目里同时引入了两个库,它们都依赖了不同版本的 Log4j。结果运行时出现了 ClassNotFoundException。嗯,这就是依赖冲突。

解决依赖冲突有几种方式:

  1. 强制指定版本:用 force = true
  2. 排除传递依赖:用 exclude
  3. 使用 BOM:统一管理版本

看代码:

dependencies {
    implementation('com.example:library-a:1.0.0') {
        exclude group: 'commons-logging', module: 'commons-logging'
    }
    implementation('com.example:library-b:2.0.0') {
        transitive = false  // 关闭传递依赖
    }
    implementation('com.example:library-c:3.0.0') {
        force = true  // 强制使用这个版本
    }
}
小技巧:gradle dependencies 命令可以查看完整的依赖树。我每次遇到依赖问题,第一件事就是跑这个命令。它能帮你快速定位冲突。

还有一种更优雅的方式——使用 BOM(Bill of Materials):

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.7.0')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.google.guava:guava'  // 版本由 BOM 统一管理
}

BOM 的好处是统一管理版本,避免手动指定。我个人非常推荐在大型项目中使用 BOM。

9.4 知识体系总览

下面这张图帮你梳理本章的核心内容:

Gradle 依赖管理核心知识体系 仓库配置 依赖声明 依赖传递与排除 mavenCentral jcenter(已停) mavenLocal 私服(自定义) implementation api compileOnly runtimeOnly 传递依赖 依赖冲突 exclude排除 BOM统一管理 核心原则 1. 能用 implementation 就别用 api 2. 遇到依赖冲突先跑 gradle dependencies 3. 大型项目推荐使用 BOM 统一管理版本
避坑指南:我曾经在一个微服务项目里,因为依赖传递导致引入了两个不同版本的 Jackson。结果序列化时各种报错。后来用 exclude 排除了其中一个版本,问题才解决。所以,依赖管理不是小事,一定要重视。

好了,关于依赖管理的内容就聊到这里。记住:仓库配置要选对,依赖声明要谨慎,依赖传递要可控。这三条做好了,你的构建基本就稳了。


公众号:蓝海资料掘金营,微信deep3321