☰
Gradle依赖下载慢:国内镜像地址与三层配置实践
2026/11/2 20:10:27 网站建设 项目流程

1. Gradle 依赖下载慢,慢的到底是哪一段

我第一次认真研究gradle国内镜像下载地址这件事,是在一个 Spring Boot 多模块项目上。clean 之后第一次构建跑了将近 40 分钟,卡在Downloading https://repo.maven.apache.org/maven2/...这一行反复刷屏,进度条像死了一样。当时第一反应是"网络问题,等等就好",但连着三天都这样,才开始动手拆解:到底是带宽不够,还是请求打到了不该打的地方。

先说结论,Gradle 构建慢在依赖下载这一段,绝大多数情况不是"网速慢",而是"请求绕远了 + 请求次数被放大了"。这两个问题性质不同,解法也不一样。带宽问题是物理限制,你换什么配置都只能等;但请求绕远和请求放大是配置问题,改几行代码就能从 40 分钟压到 3 分钟以内。这篇文章就是把这套东西讲透:镜像地址怎么找、配在哪一层、为什么这么配、踩过的坑有哪些。

适合谁看?如果你符合下面任意一条,这篇内容基本能直接用:

  • 刚接手一个 Java/Kotlin 项目,./gradlew build第一次跑就超时;
  • 团队里有人在 CI 上构建失败,本地却正常,怀疑是仓库配置不一致;
  • 想给整个开发团队统一仓库配置,不想每个项目改一遍;
  • 用过镜像但踩过"部分依赖能下、部分下不来"的半吊子状态。

需要提前说明的是,文中所有域名位置我会用https://<mirror-host>/...这种占位形式表示,你把<mirror-host>换成自己手头在用的镜像站域名即可。这么做是因为各家镜像站的域名和路径会变,写死一个具体地址反而是坑——半年后文章过期了,读者照着抄下来的地址是失效的,比没写更糟。真正稳定可复用的是路径结构和配置位置,这才是核心。

1.1 一次依赖解析,Gradle 到底在干什么

要理解为什么慢,先得知道 Gradle 拿一个依赖要走多少步。很多人以为"下载一个 jar 包"是一次 HTTP 请求,实际远不止。

假设你在build.gradle里写了implementation 'org.springframework:spring-core:6.1.0'。Gradle 在解析阶段至少会做这几件事:

第一,去你声明的每一个仓库(repositories)里,按声明顺序依次查找org/springframework/spring-core/6.1.0/spring-core-6.1.0.pom。这一步叫依赖元数据解析。注意关键词:按声明顺序依次查找。也就是说,如果你把远程中央仓库放在第一个位置,Gradle 就一定会先打它一次,等它返回结果(可能是 404,也可能是超时),才轮到第二个仓库。

第二,拿到 pom 之后,解析里面的<dependencies>、<dependencyManagement>、<parent>,递归下去。一个中等规模的 Spring Boot 项目,传递依赖展开后 150 到 300 个模块是常态。

第三,每个模块还要再校验有没有.module(Gradle Module Metadata)文件、有没有.jar、有没有 sources/javadoc(如果你开了 IDE 同步)。元数据文件往往是第一个被请求的,有些人看到日志里一堆 404 其实是正常行为。

我把这个过程在本地做了一次抓包统计,一个 180 模块的项目,冷构建阶段的 HTTP 请求总数超过 600 次。如果每个仓库前面还挂着两三个必然 404 的仓库,请求数会直接翻倍。按每次请求平均往返 200ms 算,光 404 就要多花:600 × 2 × 0.2 = 240 秒,四分钟纯浪费在"问了一个不存在的地址"上。这就是请求放大的第一层。

第二层放大是 DNS 和 TLS 握手。Gradle 对同一个 host 会复用连接,但如果你的仓库分散在三四个不同域名上,每个域名首次都要付一次 DNS 解析加 TLS 握手的成本。国内访问境外仓库,DNS 解析慢、TLS 握手更是要走好几个来回,这部分开销有时候比传输本身还大。

所以"换国内镜像"的本质,是把"跨境的长往返"换成"境内的短往返",同时把 404 的次数压下来。理解这一点,后面所有配置选择就都有了依据。

1.2 三个可以下手的层次,别搞混

新手最容易犯的错,是把所有"下载慢"都归结为同一个问题,然后抄一个配置贴进去,结果有的项目快了,有的还是慢。实际上 Gradle 生态里的下载行为分三个完全独立的层次,配置位置各不相同:

第一个层次是项目依赖(dependencies)。这是最常见的,implementation、api、testImplementation这些。它们走的是repositories { }块里声明的仓库,配置位置在build.gradle或者settings.gradle的dependencyResolutionManagement里。

第二个层次是插件依赖(plugins)。plugins { id 'xxx' version '1.0' }这种写法,默认走的是 Gradle Plugin Portal,跟你在repositories里配的东西完全无关。这是最经典的坑:有人兴冲冲配好了镜像,结果发现 Spring Boot 插件还是下不来。插件仓库要单独在pluginManagement { repositories { } }里配。

第三个层次是 Gradle 发行包本身(Wrapper)。项目根目录下的gradle/wrapper/gradle-wrapper.properties里有一行distributionUrl,默认指向services.gradle.org。这个文件由 wrapper 机制自己下载,跟你写的任何仓库配置都没关系。一个gradle-8.x-bin.zip一百多兆,下载慢的时候能卡十几分钟。

这三个层次必须分别处理,缺一个就会出现"我明明配了镜像怎么还是慢"的困惑。我在排查别人问题的时候,第一句话往往是:你先看日志卡住的那一行 URL 长什么样,是repo.maven.apache.org、plugins.gradle.org还是services.gradle.org,三个指向的是三套配置。

1.3 挑镜像之前,先看这四条标准

市面上的公共仓库镜像不止一家,选哪个不是看谁广告打得响。我一般按下面四条来筛:

评估维度为什么重要怎么验证
同步频率中央仓库发版当天没同步到,你构建就挂找一个近期发布的版本,直接访问元数据 URL 看是否存在
覆盖范围只代理中央仓库的话,Google、Spring、Gradle 插件类依赖依然要出境分别试探 central、spring、google、gradle-plugin 几个路径是否可用
协议支持有些镜像只开 HTTPS,有些内网镜像只有 HTTP,配置方式不同浏览器直接访问,看是否强制跳转
稳定性与限流免费公共镜像高峰期会限速甚至返回 429连续跑几次冷构建,观察是否有间歇性失败

注意:镜像站不是越多越好。我见过有人一口气配了五个仓库,结果 404 次数暴涨,构建反而更慢。实际经验是,三个以内足够:一个覆盖中央仓库和常见生态的聚合仓库、一个 Spring 类仓库(如果项目用 Spring)、一个插件仓库。

还有一点很多人忽略:镜像站的"覆盖范围"是会变的。有的镜像早期只代理中央仓库,后来才加上 Google 和 Gradle 插件。所以你手上那份配置如果是一年前从某篇帖子抄的,路径很可能已经不全了。养成习惯,配置完之后手动验证几个关键路径能不能访问,比盲目相信配置有效。

2. 镜像地址的构成逻辑与仓库声明原则

2.1 公共镜像站的路径约定,其实很统一

虽然各家域名不同,但国内主流的 Maven 公共镜像站,路径命名上有相当强的一致性。理解这个约定,你就不需要死记某个具体地址,换个镜像站也能自己拼出来。

最常见的一类路径是/repository/<name>,其中<name>是仓库标识符。常见的标识符有这么几个:

  • public:聚合仓库,把中央仓库、Spring、Google 等几类合并在一起,最省事,也是我最常配的一个;
  • central:只代理 Maven 中央仓库,覆盖最广,但只覆盖中央;
  • spring/spring-plugin:Spring 生态的构件,有些 Spring 里程碑版本只在专门仓库里有;
  • google:Android 相关构件(AndroidX、Compose 等);
  • gradle-plugin:Gradle 插件门户的代理,配在pluginManagement里。

所以一个典型的镜像地址长这样:https://<mirror-host>/repository/public。你在浏览器里直接贴这个地址加一段路径,比如/repository/public/org/springframework/spring-core/6.1.0/spring-core-6.1.0.pom,如果能看到 XML 内容,说明这个仓库是通的。这是我最推荐的验证手法——不要等 Gradle 报错才去查,配置前先浏览器访问一次,30 秒就能确认路径对不对。

另一类常见路径是/maven、/maven2这种更简短的写法,多见于一些企业自建或社区维护的镜像。路径结构千变万化,但本质都是"一个 Maven 仓库布局的 HTTP 静态服务"。你只要确认三件事:根路径下能访问group/artifact/version/artifact-version.pom、支持 HTTPS(或明确知道是 HTTP)、没有强制的登录鉴权。

2.2 仓库顺序为什么比仓库数量更关键

这一点值得单独拎出来讲,因为它是"配了镜像但还是慢"的头号原因。

Gradle 解析依赖时,对repositories块里的仓库是严格按声明顺序串行尝试的。它不是并发去问所有仓库,而是一个一个来。第一个仓库返回 200 就用它,返回 404 就问下一个,超时就……这个行为比较微妙,取决于具体版本和配置,但整体是"前面的优先"。

所以下面这种写法是灾难:

repositories { mavenCentral() // 第一个,境外,每次必问 maven { url 'https://<mirror-host>/repository/public' } }

看起来"镜像作为补充",实际上每个依赖都要先去境外问一次,命中失败才走镜像。等于把最慢的路径放在了最前面。冷构建时这个代价被放大几百倍。

正确的顺序是把镜像放前面:

repositories { maven { url 'https://<mirror-host>/repository/public' allowInsecureProtocol = false } mavenCentral() // 兜底,镜像没同步到的用它 }

这里有个取舍要讲清楚:镜像放第一,意味着每个依赖都先问镜像。如果镜像没同步到某个构件,会先吃一次 404,再走兜底。这和"境外放第一"的区别在于,境内 404 的往返成本远低于跨境超时的成本,所以整体仍然是赚的。

提示:mavenCentral()保留作为兜底是有意义的,尤其是用了 SNAPSHOT 或者刚发布几小时的版本。但如果你追求极致速度、项目依赖又很稳定,也可以直接只留镜像。我的习惯是保留兜底,因为 CI 上偶尔需要拉新版本,多一次 404 换一次构建不失败,值得。

allowInsecureProtocol这个参数要提一下。Gradle 7 之后默认拒绝 HTTP 协议(非 HTTPS)的仓库地址,如果镜像站只有 HTTP,会报错提示你需要显式声明。这时候加allowInsecureProtocol = true才能用。但这等于把传输降级为明文,只建议在完全可控的内网环境使用,公网场景一律走 HTTPS。

2.3 Wrapper 发行包和插件门户,必须单开小灶

前面说过这两个层次独立,这里说具体怎么处理。

Wrapper 发行包,改gradle/wrapper/gradle-wrapper.properties里的distributionUrl:

distributionUrl=https\://<mirror-host>/distributions/gradle-8.7-bin.zip

注意两点:一是冒号要转义成\:,这是 properties 文件里 URL 的写法要求,漏了会解析出错;二是路径里的/distributions/是很多镜像站的约定,下载的 zip 文件名跟官方保持一致(gradle-<version>-bin.zip或-all.zip)。-bin只有运行时,-all还包含源码和文档,体积大不少。日常开发用-bin就够,IDE 里要看 Gradle 源码时才需要-all。

改完这个文件要提交到版本库,否则同事拉下来还是走官方地址。但这里有个坑:如果团队里有人在外网环境(比如家里网络能直连官方),你把地址改成了内网镜像,他那台机器就挂了。所以这一行最好做成可覆盖的形式,或者干脆在团队内部统一约定。

插件门户,配在settings.gradle的pluginManagement里:

pluginManagement { repositories { maven { url 'https://<mirror-host>/repository/gradle-plugin' } maven { url 'https://<mirror-host>/repository/public' } } }

这里我配了两个:先插件专用仓库,再聚合仓库兜底。原因是有些插件虽然发布到了 Plugin Portal,但它的 marker artifact 和实际构件可能分散在不同地方,多配一层能减少失败率。pluginManagement块必须放在settings.gradle的最前面,放在别的内容后面会报错,这是 Gradle 的硬性要求。

3. 三套配置方案,按项目规模选

3.1 方案一:项目内声明,适合单项目快速验证

如果你只是想先试试效果,最直接的办法是改项目自己的settings.gradle(Gradle 6.8 之后推荐把仓库统一放在这里,而不是每个子模块的build.gradle)。

Groovy DSL 版本:

// settings.gradle dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { maven { url 'https://<mirror-host>/repository/public' allowInsecureProtocol = false } maven { url 'https://<mirror-host>/repository/spring' } mavenCentral() } } rootProject.name = 'demo-service' include 'core', 'api'

Kotlin DSL 版本(settings.gradle.kts):

dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { maven { url = uri("https://<mirror-host>/repository/public") isAllowInsecureProtocol = false } mavenCentral() } }

repositoriesMode这个设置很关键,容易被忽略。它有四个取值,我用得最多的是PREFER_SETTINGS和FAIL_ON_PROJECT_REPOS:

取值行为适用场景
PREFER_SETTINGS优先用 settings 里声明的,子模块里写的会被忽略并告警渐进式统一,老项目过渡期
FAIL_ON_PROJECT_REPOS子模块只要写了 repositories 就直接构建失败新项目、强管控团队
PREFER_PROJECT子模块的优先(默认行为)老项目兼容

我推荐新项目直接用FAIL_ON_PROJECT_REPOS。听起来很激进,但好处是仓库配置只有一处,改镜像只改一个文件,不会出现"某个子模块偷偷配了个境外仓库"的情况。老项目过渡期用PREFER_SETTINGS,跑一段时间把告警清干净,再切到严格模式。

注意:切到FAIL_ON_PROJECT_REPOS之后,如果某个子模块的build.gradle里还留着repositories { mavenCentral() },构建会直接报错,报错信息会明确指出是哪个文件哪一行。按提示删掉就行,不要图省事改回宽松模式,否则过两周又乱了。

3.2 方案二:全局 init 脚本,团队和个人的最优解

项目内配置的问题是:每接一个新项目都要改一遍。如果你手上有七八个仓库在同时维护,这个重复劳动很烦人。这时候该上init.gradle。

它的位置在用户目录下的.gradle/init.d/里(Linux/macOS 是~/.gradle/init.d/,Windows 是%USERPROFILE%\.gradle\init.d\),文件名任意,后缀.gradle就行,比如mirrors.gradle。这个目录下的所有脚本会在每个Gradle 构建开始前执行,不需要任何项目配合。

下面这份是我目前在用的,注释比较全:

// ~/.gradle/init.d/mirrors.gradle // 作用:全局替换仓库地址,不改动任何项目文件 def MIRROR_PUBLIC = 'https://<mirror-host>/repository/public' def MIRROR_SPRING = 'https://<mirror-host>/repository/spring' def MIRROR_PLUGIN = 'https://<mirror-host>/repository/gradle-plugin' // 1. 处理 settings 阶段的仓库(dependencyResolutionManagement 和 pluginManagement) beforeSettings { settings -> settings.pluginManagement { repositories { maven { url MIRROR_PLUGIN } maven { url MIRROR_PUBLIC } gradlePluginPortal() } } } // 2. 处理项目级仓库(针对老项目、没有用 dependencyResolutionManagement 的) allprojects { buildscript { repositories { maven { url MIRROR_PUBLIC } mavenCentral() } } repositories { maven { url MIRROR_PUBLIC } maven { url MIRROR_SPRING } mavenCentral() } }

几个设计点解释一下。

为什么用beforeSettings而不是settingsEvaluated。beforeSettings在 settings 脚本被解析之前执行,这时候插进去的pluginManagement仓库才会生效。如果用settingsEvaluated,插件解析阶段早就结束了,配了也白配。这是很多人配了全局脚本但插件依然下不来的原因。

为什么allprojects里还要再配一遍。因为beforeSettings只管 settings 阶段。如果某个老项目完全没写dependencyResolutionManagement,仓库声明散落在各个build.gradle里,那就只能靠allprojects兜底。但要注意,allprojects里加的仓库会追加在项目已有仓库之后,而不是替换。所以如果你项目里第一个写的是mavenCentral(),全局脚本里的镜像依然排在它后面,效果打折。

想彻底解决,得在allprojects里做一次"清空再写入":

allprojects { repositories { clear() // 清掉项目自己声明的 maven { url MIRROR_PUBLIC } mavenCentral() } }

clear()有风险——如果某个项目依赖了私有仓库(公司内部的 artifact 服务),会被一起清掉,导致构建失败。所以我的做法是:先不清空,观察一段时间日志,如果确认项目里没有私有仓库,再开启clear()。这条经验是用一次 CI 全线飘红换来的,别省这一步。

为什么脚本里用变量而不是直接写字符串。镜像站地址会变,尤其是公司更换供应商或者自建镜像迁移的时候。把地址提到文件顶部定义成变量,改一处就够了,比在文件里全局搜索替换安全。

3.3 方案三:属性文件与环境变量,给"不可改文件"的场景留后路

还有一种情况:项目是别人的、文件不能改,或者 CI 环境的初始化脚本不方便加。这时候可以用gradle.properties。

在用户目录的~/.gradle/gradle.properties里可以定义一些属性,虽然它不能直接改仓库地址,但能影响构建行为。比如:

# 提升并行下载能力(Gradle 7.4+) org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=false # 网络超时,默认值在弱网下太短 systemProp.org.gradle.internal.http.connectionTimeout=60000 systemProp.org.gradle.internal.http.socketTimeout=60000 systemProp.org.gradle.internal.repository.max.tentatives=3 systemProp.org.gradle.internal.repository.initial.backoff=500

connectionTimeout和socketTimeout这两个参数值得单独说。Gradle 默认的连接超时比较短,跨境访问时经常还没建立连接就超时了,然后触发重试,日志里表现为"卡住很久然后报错"。把超时拉长到 60 秒,很多时候"构建失败"就变成了"慢但成功"。重试次数max.tentatives设为 3 配合退避initial.backoff,对间歇性 429 限流也有帮助。

但要说清楚:调大超时是止痛药,不是治本。它的作用是让偶发抖动不至于中断构建,真正解决速度问题还是得靠镜像。

环境变量这条路可以控制 Gradle 自身的运行环境,但没法用来改仓库地址,网上有些说法是错的。真正能做的是在 CI 的启动脚本里加上初始化逻辑,比如构建前先判断~/.gradle/init.d/下有没有文件,没有就写一份进去。这是我在几个 CI 流水线里用过的做法,比在每个项目的settings.gradle里写镜像要干净得多。

3.4 怎么确认配置真的生效了

配完不代表生效,必须验证。我一般走三步。

第一步,看日志里的实际 URL。跑一次冷构建,加上--info:

./gradlew build --refresh-dependencies --info 2>&1 | grep -i "downloading\|resource"

--refresh-dependencies强制重新解析依赖,绕过本地缓存。日志里如果出现的是你的镜像域名,说明生效了;如果还是repo.maven.apache.org,那就是配置位置不对,或者被项目里的声明覆盖了。

第二步,对比耗时。我会记两个数字:改之前的冷构建耗时、改之后的冷构建耗时。测试时要先清缓存:

# 清理依赖缓存(谨慎操作,清完第一次构建会很慢) rm -rf ~/.gradle/caches/modules-2/files-2.1 # 或者更温和的方式,只清理元数据,保留 jar ./gradlew build --refresh-dependencies

我自己的实测记录(一个 180 模块的 Spring Boot 项目,家用宽带):

阶段优化前优化后说明
依赖元数据解析约 12 分钟约 50 秒主要省在 404 和跨境往返
构件下载(约 340MB)约 22 分钟约 3 分钟带宽是主要瓶颈,镜像能拉满
Wrapper 首次下载(130MB)约 15 分钟约 1 分钟走镜像 distributions 路径
插件解析约 5 分钟约 20 秒pluginManagement单独配置后

这组数字不是标准答案,你的网络环境不同,绝对值会差很多,但相对比例有参考价值:优化收益最大的往往是元数据解析和插件解析,而不是构件下载本身。这也印证了前面说的,慢的主因是请求次数被放大,不是纯带宽。

第三步,验证私有依赖没被打断。如果你的项目依赖了公司内部的 artifact 仓库,一定要在改完之后单独验证那几个模块能不能正常解析。我见过太多"配了镜像结果私有包全挂"的案例,原因就是clear()把私有仓库一起清了,或者镜像的优先级压过了私有仓库。

4. 报错排查实录与高频坑位

4.1 高频报错对照表

下面这些报错我在不同项目里都遇到过,整理成表方便对照:

报错关键字真实原因处理方式
Could not resolve ...+ 404仓库路径对但构件没有换兜底仓库,或确认版本号是否写错
Could not GET 'https://...'+Connect timed out网络不可达或超时太短换镜像;同时调大connectionTimeout
Using insecure protocols with repositories配了 HTTP 地址但没声明加allowInsecureProtocol = true,或换成 HTTPS
Plugin [id: 'xxx'] was not found插件仓库没配在pluginManagement里加插件镜像
Received status code 429镜像站限流降低并发,或增加退避重试
Could not resolve ... No matching variant不是网络问题,是元数据不兼容检查 Java 版本与依赖的 target 是否匹配
Read timed out但重试能过弱网 + 大文件调大socketTimeout,重启构建

重点说两个容易误判的。

第一个,No matching variant根本不是网络问题。有些人看到"Could not resolve"就以为是镜像不行,改了半天配置发现没用。这个报错是 Gradle 的变体感知(variant-aware)解析在说"这个库有这个版本,但它提供的变体和你的运行环境不匹配",典型场景是 Java 版本不对(比如依赖要求 JDK 17 但你用 JDK 11)、或者 Android 依赖的 ABI 不匹配。遇到这个先看完整报错里的required和found部分,别急着动仓库配置。

第二个,Received status code 429说明镜像站在保护自己。公共镜像免费给大家用,高峰期有限流很正常。处理思路不是"再试一次",而是降低冲击:减少并发(org.gradle.parallel=false)、减少仓库数量、错峰构建。CI 上尤其明显,几十个 job 同时跑,每个都去拉一遍,很容易触发限流。正经做法是在 CI 环境里做一层依赖缓存,把~/.gradle/caches缓存起来复用。

4.2 依赖能下,插件死活下不来

这是最高频的"半吊子生效"问题,值得单独展开。

现象是:implementation的依赖都正常下载了,但一跑到插件就报Plugin [id: 'org.springframework.boot', version: '3.x.x'] was not found。

原因几乎百分百是:镜像只配在了repositories里,没配在pluginManagement.repositories里。

Gradle 的插件解析走的是独立通道。你写plugins { id 'xxx' version '1.0' },Gradle 会去 Plugin Portal(plugins.gradle.org/m2)找这个插件的 marker artifact,跟你配的项目仓库毫无关系。所以必须单独在settings.gradle顶部写:

pluginManagement { repositories { maven { url 'https://<mirror-host>/repository/gradle-plugin' } maven { url 'https://<mirror-host>/repository/public' } gradlePluginPortal() } }

同时提醒一个细节:pluginManagement块必须是settings.gradle的第一个块,前面不能有任何其他语句(注释可以)。如果放在rootProject.name后面,Gradle 会报错说pluginManagement {} block must appear before any other statements。这个错误信息还算清晰,照做就行。

还有一种更隐蔽的情况:插件本身能找到,但插件内部依赖的库找不到。因为插件的类路径(buildscript classpath)跟项目的依赖类是两条独立的解析链路。这种情况在buildscript { repositories { } }里也要加镜像。所以我在init.gradle里两处都配了,就是防这个。

4.3 缓存污染的识别和清理手法

Gradle 的依赖缓存很强,但也很容易脏。尤其是你中途改过仓库配置、或者某次下载被中断,缓存里可能留下不完整的东西,导致后面怎么配都报错。

识别信号:明明浏览器能访问的地址,Gradle 就是报Could not resolve;或者报Could not read/corrupt之类的关键字。

清理要分级,别一上来就全删:

# 一级:只刷新依赖,最温和 ./gradlew build --refresh-dependencies # 二级:停掉守护进程再刷(改了 JVM 参数或 init 脚本后需要) ./gradlew --stop ./gradlew build --refresh-dependencies # 三级:删单个模块的缓存(知道是哪个依赖出问题时用) # 路径形如 ~/.gradle/caches/modules-2/files-2.1/<group>/<artifact>/ rm -rf ~/.gradle/caches/modules-2/files-2.1/org.springframework # 四级:全删模块缓存(大杀器,慎用) rm -rf ~/.gradle/caches/modules-2

注意:改了init.gradle之后一定要先./gradlew --stop。Gradle 守护进程会缓存构建逻辑,不重启的话新脚本可能完全不生效,你会以为配置写错了,白折腾半天。这一条我踩过至少三次。

还有个小技巧:如果怀疑是某个特定依赖有问题,可以单独跑一个诊断任务,只解析不编译:

./gradlew :app:dependencies --configuration compileClasspath --refresh-dependencies

这个命令会打印完整的依赖树,哪个节点被解析到了哪个仓库、有没有发生版本冲突,一目了然。比盲猜快得多。

5. 长期维护这套配置的几点经验

配置一次不难,难的是半年后它还work。分享几个我实际维护中总结的做法。

5.1 镜像不能只有一条路,得留降级方案

公共镜像站会维护、会迁移、会临时故障。如果你把所有配置都绑死在一个镜像站上,它一挂,整个团队停摆。我现在的做法是主备两套,通过一个开关控制:

// ~/.gradle/init.d/mirrors.gradle def USE_PRIMARY = true def PRIMARY = 'https://<mirror-host-a>/repository/public' def BACKUP = 'https://<mirror-host-b>/repository/public' def activeMirror = USE_PRIMARY ? PRIMARY : BACKUP

主站出问题的时候,改一个布尔值,--stop一下,全团队重新构建就切过去了。听起来很土,但比在十几个项目里改配置快得多。

更进一步的做法是把镜像地址放到~/.gradle/gradle.properties里,init.gradle去读:

// gradle.properties mirror.public=https://<mirror-host-a>/repository/public mirror.spring=https://<mirror-host-a>/repository/spring
// init.gradle def props = new Properties() def f = new File(gradle.gradleUserHomeDir, 'gradle.properties') if (f.exists()) f.withInputStream { props.load(it) } def MIRROR_PUBLIC = props.getProperty('mirror.public', 'https://<fallback>/repository/public')

这样脚本和配置分离,切换镜像不用动脚本本身,也方便用环境变量在 CI 里覆盖。

5.2 团队协作里,统一和例外要一起考虑

统一仓库配置的好处不用多说,但落地时一定会遇到"例外"。比如某个项目要依赖公司内网的 artifact 服务,某个老项目用了废弃的 jcenter 仓库还有些残留构件,某个同事的机器在公司外网环境。

我的处理原则是:默认统一,例外显式声明。

具体做法是在init.gradle里判断,如果项目根目录存在一个标记文件(比如use-custom-repos.flag),就跳过全局替换,让项目自己管;否则就走统一配置。这样既保证了 90% 的项目配置一致,又给 10% 的特殊情况留了出口,而且这个出口是显式可见的——谁开了例外,看文件就知道,不会变成无人知晓的隐性配置。

def customFlag = new File(rootDir, 'use-custom-repos.flag') if (!customFlag.exists()) { allprojects { repositories { maven { url MIRROR_PUBLIC } mavenCentral() } } }

另外一点是文档。这套配置散落在init.gradle、settings.gradle、gradle-wrapper.properties三处,新人接手很容易漏。我在项目 README 里固定放一段"构建环境说明",写清楚这三处的作用和修改方式,比口头交接靠谱。

最后分享一个我觉得最有用的习惯:每次大版本升级 Gradle 之后,重新验证一遍镜像配置。Gradle 大版本之间对仓库 DSL、repositoriesMode的默认值、HTTP 协议的限制都可能有调整,比如某个版本开始默认禁止 HTTP 仓库、某个版本调整了pluginManagement的解析时机。升级完跑一次--info冷构建,看日志里的域名和耗时,五分钟的事,能避免后面一堆莫名其妙的构建失败。我在 7.x 升 8.x 的时候就因为没做这一步,被一个默认值变更坑了整整一个下午。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询