Maven 拉依赖慢,几乎是每个在本地做 Java 开发的人都会撞上的问题。新拉一个项目,第一次执行mvn clean package,进度条像卡住了一样,控制台不停输出Downloading from central,动辄十几分钟乃至直接超时失败,那种体验我到现在都记得。要解决这个问题,最常规、最有效的办法就是给 Maven 配置国内镜像仓库。所谓镜像,就是在我方网络环境下建立一个中央仓库的同步副本,下载依赖时不再直接访问海外服务器,而是走国内节点,速度自然就上来了。
这篇内容主要讲清楚镜像配置的前因后果,以及真正可以抄作业的配置方案。从为什么慢、镜像怎么工作,到配置文件在哪、mirror 节点怎么写、多镜像怎么搞,再到配完以后常见的一堆坑,我都会按自己实际用下来的经验顺序捋一遍。适合刚接触 Maven 的小白,也适合那些配过镜像但总在某个边角问题上折腾半天的老手。
1. 依赖下载慢的根源与镜像的工作方式
1.1 卡点到底在哪:请求都发去了海外
Maven 默认的远程仓库只有一个,就是官方中央仓库。官方仓本身是好的,问题是它架设在海外网络环境下,而我们的开发网络访问境外服务器时,延迟高、丢包率也不低。下载一个小型依赖可能看不出什么,但一个真实项目的依赖树往往有几百甚至上千个 jar 包,每个 jar 还要连带下载 pom、校验和文件等,累计起来的请求数量非常可观。
我在帮朋友排查过一次,一个 Spring Boot 聚合工程,首次构建光是依赖就拉了近 400MB。从官方仓库拉,速度经常掉到几十 KB/s,中间还会整包超时重试。Maven 对失败的依赖下载有重试机制,但每次重试都要重新握手、重新建立连接,时间成本比想象中高得多。这不是单次请求慢的问题,而是整个依赖获取链路在网络上被拖住了。
而且还有一个容易被忽略的点:构建工具在解析依赖时是按顺序检查仓库的,不是所有 jar 并行下载。一旦碰到某个大体积依赖卡住,后续依赖全部排队等待,构建时间就会被无限拉长。因此,替换仓库地址到国内节点,等于把整个下载链路的地理位置搬到了自家门口,效果几乎是立竿见影的。
1.2 镜像到底做了什么:请求转发与缓存同步
镜像仓库的机制理解起来很简单:你在 Maven 里声明一个 mirror,Maven 在对中央仓库发请求之前,会先看有没有镜像配置,有的话就直接把请求转向镜像地址。
镜像服务器做的事情,本质上就是定期把官方中央仓库的内容同步到自己的服务器上。你的下载请求到达镜像站后,如果文件已经同步过,就直接返回;如果没有,镜像会触发一次回源拉取。同步通常有延迟,所以偶尔会出现某个刚发布的新版本在镜像上还找不到的情况,这是正常现象,等待同步即可。
需要特别区分一下:mirrors 和 repositories 是两个层面的东西。repositories 是告诉 Maven “我要用哪些仓库”,mirrors 则是“当我访问某个仓库时,实际走哪个地址”。镜像的优先级非常高,只要 mirror 配置命中了目标仓库 id,Maven 就不会再访问原始地址。理解这一点之后,很多“配了不生效”“私服被劫持”之类的问题就有了解释。
2. 动手之前先弄清配置文件和关键节点
2.1 找对配置文件:全局还是用户级
Maven 的配置入口有两个:安装目录下conf/settings.xml是全局配置,对所有用户生效;用户目录下~/.m2/settings.xml是用户级配置,只对当前用户生效。如果两处都存在,Maven 会把它们合并,用户级配置覆盖全局配置。
我的建议是优先配置用户级的~/.m2/settings.xml,理由很实际:不需要动系统安装目录,也就不需要管理员权限,而且换机器、换项目时只要把这一个文件拷贝走,环境就跟着走了。很多人的 Maven 是解压版,散落在某个不算规范的目录,直接改conf/settings.xml也行,但团队协作时容易被别人覆盖掉。
还有一个高频坑,尤其在使用集成开发环境时要注意:IDE 内置了一套 Maven。比如 IntelliJ IDEA 默认使用的可能是它自带的 Maven,而不是你在系统里单独安装的版本。如果你只改了系统 Maven 的配置文件,而 IDE 里用的还是内置的那套,那配置自然不生效。正确做法是在 IDE 的 Maven 设置页里,把 Maven home 指向自己安装的目录,同时确认 User settings file 路径指向~/.m2/settings.xml。
2.2 settings.xml 里决定镜像生效的几个节点
配置文件里和镜像直接相关的节点就一个:mirrors。它下面可以有多个mirror,每个mirror包含四段关键信息:
id:镜像的唯一标识,理论上随意,但不能和其他仓库 id 重复。name:镜像名称,纯展示用。url:镜像仓库的地址,这是最关键的一段。mirrorOf:声明这个镜像拦截哪些仓库,支持通配符和排除规则。
很多人配完不生效,八成是mirrorOf写错了。它不是写着玩的,它必须和你要拦截的仓库 id 匹配。官方中央仓库在 Maven 里默认的 id 就是central,所以平时配置镜像用的就是<mirrorOf>central</mirrorOf>。
有的教程喜欢写<mirrorOf>*</mirrorOf>,意思是拦截所有仓库请求,省事但也埋雷。如果项目里配置了公司内部的私有仓库,*会把私有仓库也劫持到公共镜像上,私有构件就会全部拉不到,这个问题后面详细讲。
3. 国内公共镜像的选择与取舍
3.1 常见的几类镜像仓库对比
国内可用的公共 Maven 镜像不少,来源主要分两类:一类是各大云厂商提供的公共仓库镜像,一类是开源社区维护的镜像站。它们的共性是部署在国内节点,带宽充足,同步频率较高,对绝大多数项目来说都够用。
这里我没法直接给你写死某个具体截图上的地址,因为镜像站偶尔会调整发布路径,且不同服务商的仓库路径差异很大。一个通用规律是:镜像地址通常以https://开头,路径里会带maven或repository这样的标识,比如常见的路径风格有/repository/maven-public/、/maven2/等。配置之前,先去你选定的镜像站首页找到 Maven 仓库对应的 URL,复制完整路径再填进去,比凭记忆手写可靠得多。
我把常见的几类镜像整理成了一张对照表,方便你根据场景做选择:
| 镜像类型 | 典型路径风格 | 特点 | 适合场景 |
|---|---|---|---|
| 云厂商公共镜像 | /repository/maven-public/、/maven2/ | 同步快、带宽大、稳定高 | 个人开发、外网环境下的默认选择 |
| 开源社区镜像 | /maven/ | 更新频率尚可,覆盖语言多 | 轻量项目,对版本及时性要求不高 |
| 公司内网私有源 | /repository/maven-public/ | 由内部团队维护,可控性强 | 团队协作、有私有构件、离线环境 |
3.2 该不该同时配好几个镜像
很多人在配置的时候有一种“多配几个更保险”的心态,主页面上也不止一个镜像源,就全部塞进mirrors列表里。这里要纠正一下:Maven 对 mirror 的处理是顺序匹配,而不是并发请求取最快结果。它会从上到下逐个找第一个能匹配当前仓库的 mirror,然后使用它。如果第一个配置不稳定,Maven 并不会自动切换到第二个去重新下载同一个依赖,而是等请求失败后再试下一个,这期间的超时等待反而更慢。
所以我的结论是:选一个质量最稳定的主镜像就够了,不需要把三四个源全填进去。备用的需求可以通过 profile 切换来实现,而不是靠多写 mirror 硬堆。至于私有源和公共源的关系,也不适合用多个 mirror 去碰运气,应该用仓库声明的方式做区分。
4. 完整配置步骤与验证方法
4.1 最小可用的镜像配置写法
直接用文件覆盖的方式,写一个最小配置:
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <mirror> <id>cn-mirror</id> <name>China Mirror</name> <url>https://mirrors.cn-example.example/maven-public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> </settings>上面的url是示例地址,实际使用时一定要去你选择的镜像站首页复制完整地址,替换掉mirrors.cn-example.example这段及后续路径。不同云厂商的路径前缀不一样,有的带repository,有的直接就是根路径下,照抄别人的地址很容易碰上 404。
mirrorOf这里写成central,也就是只拦截官方中央仓库的请求。这是我认为最不会出错的写法。外部私有仓库、其他公共仓库都不受影响,以后项目里需要访问私有源时也不会有冲突。
4.2 验证配置是否真正生效
配置完成后不要急着跑完整构建,先用一条命令验证:
mvn help:effective-settings这条命令会输出 Maven 合并所有配置之后的最终状态,你要看的内容集中在<mirrors>节点,确认里面显示的 URL 和你写的一致,且mirrorOf是central。
光看配置还不够,再跑一次实际的依赖解析,观察下载日志:
mvn dependency:resolve正常生效后,日志里的Downloading from后面跟着的就不再是官方仓库地址,而是你配置的镜像地址。这是最直观的验证方式。我第一次配镜像时,日志里看起来还是官方仓库地址,排查半天才发现是 IDE 用了内置 Maven,压根没读我改的配置文件。
另外,本机已有的缓存可能会干扰判断:如果某个依赖之前在默认仓库下已经下载成功,Maven 就不会再发起下载请求,也就看不到下载日志。此时可以先在项目里加一个新的、之前没用过的依赖,再执行mvn dependency:resolve,能更快看到新地址是否生效。
4.3 快照依赖和 mirror 的关系
快照版本(带-SNAPSHOT后缀的版本)在 Maven 里默认是关闭下载的,除非仓库配置里显式开启。公共镜像一般会把中央仓库的快照内容也同步,但很多项目并不依赖外部快照,所以多数场景下不用关心。
真正要注意的是,如果你自己的组件发布到了私有仓库,而私有仓库的快照需要走内部网络拉取,此时你的 mirror 配置必须能够识别并跳过这个私有仓库。mirrorOf写成central就能实现这个效果,这也是我坚持推荐central而不是*的核心原因之一。
5. 进阶配置:多镜像、环境切换与 mirrorOf 规则
5.1 多镜像顺序与超时策略的正确用法
先明确一个原则:多 mirror 的初衷不是提高下载速度,而是在主镜像不可用时提供降级通道。因此,把最稳定的镜像写在前面,备用镜像写在后面。举例如下:
<mirrors> <mirror> <id>primary-mirror</id> <name>Primary Mirror</name> <url>https://mirrors.cn-example.example/maven-public/</url> <mirrorOf>central</mirrorOf> </mirror> <mirror> <id>backup-mirror</id> <name>Backup Mirror</name> <url>https://mirrors.backup-example.example/repository/maven-public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>这里有一个很容易踩的坑:当第一个 mirror 连接超时时,Maven 会等待超时结束后才能尝试下一个。如果你把备用镜像填在后面,却把超时时间设得很长,那备用镜像的优势基本体现不出来,构建照样卡住。所以,如果主镜像稳定性足够好,我建议干脆不要配备用;如果确实需要备用,可以在调用 Maven 时增加超时参数,控制失败切换的时间成本:
mvn clean package -Dmaven.wagon.http.connectionTimeout=10000 -Dmaven.wagon.http.readTimeout=30000这两个参数分别控制连接建立和读取数据的超时毫秒数,实测下来能让失败切换快很多。有些项目用的是新版 Maven 自带的 resolver 传输,参数名可能不一样,但多数情况下这两个还是有效的。
5.2 用 profile 区分内网与外网环境
mirror 适合“所有项目统一走某个源”的简单场景。但真实团队环境往往复杂得多:在家办公时希望走公共镜像,回到公司内网又需要走内部仓库拉取私有构件。这种环境切换用 mirror 就不够灵活了,改用 profile 是更合理的做法。
在settings.xml里定义一个内网专用的 profile,激活条件是activeByDefault,然后通过命令行参数切换:
<profiles> <profile> <id>internal</id> <repositories> <repository> <id>internal-repo</id> <url>http://nexus.internal.example/repository/maven-public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </profile> </profiles> <activeProfiles> <activeProfile>internal</activeProfile> </activeProfiles>默认激活 internal,走内网源;需要切换公共源时,用-P !internal关闭即可。这种方案的灵活性在于项目 POM 里还可以继续声明其他仓库而不受全局 mirror 影响,构建行为的可解释性强很多。我在几家公司的实际项目里都用这个模式,比来回改 mirror 省心得多。
5.3 mirrorOf 完整规则:会通配,更要会排除
mirrorOf的匹配规则比想象中灵活,掌握之后可以精准控制镜像范围:
central:只镜像中央仓库。*:镜像所有仓库。external:*:镜像除本地文件仓库外的所有外部仓库。repo1,repo2:镜像指定的多个仓库。*,!internal:镜像所有仓库,但排除internal。
最常用的就是第一种和最后一种。external:*配合公共镜像,可以覆盖所有外部依赖来源,适合纯公开开源项目的开发环境。但如果你同时维护私有索引,就必须学会排除写法。比如公司 Nexus 仓库 id 是nexus-private,那么可以这样:
<mirror> <id>cn-mirror</id> <name>China Mirror</name> <url>https://mirrors.cn-example.example/maven-public/</url> <mirrorOf>*,!nexus-private</mirrorOf> </mirror>这样外网构件都走镜像,私有构件还是从公司地址拉取,两者互不干扰。
6. 常见问题与排查实录
6.1 配置了镜像但日志显示还在访问默认仓库
这个问题的出现频率极高,原因通常有三个。
第一,改的是全局配置,但构建工具实际用的是内置 Maven 的配置,前面提到过,IDE 场景最典型。排查方法很简单:在终端里直接执行mvn -v看 Maven 版本路径,再确认 IDE 的 Maven home 指向一致。
第二,mirrorOf没写对。有人写了<mirrorId>central</mirrorId>这种不存在的节点,或者把mirrorOf写成了别的仓库 id,匹配不上自然不生效。检查时不光看 URL,还要确认mirrorOf的值和日志里Downloading from后面的仓库标识一致。
第三,项目 POM 里显式声明了仓库,且仓库 id 不是central。此时请求的是自定义仓库,mirror 只拦了central,请求就不会被接管。可以通过mvn help:effective-pom查看项目最终生效的仓库 id,再调整mirrorOf。
6.2 配置 mirror 后公司私服的依赖全部拉不到
这基本就是mirrorOf写成*造成的。比如项目在 POM 里声明了公司私服,id 是company-repo,而全局 mirror 是mirrorOf = *,所有仓库请求都会被转向镜像地址。私服上的私有构件在镜像站上当然不存在,构建就会报错。
解决方法是把mirrorOf改为排除式写法:*,!company-repo。还有一种更保守的做法,干脆只写central,让私服完全不受镜像影响。我在真实项目中普遍采用排除写法,因为它兼顾了公共依赖的加速和私有依赖的直达。
6.3 下载速度依旧很慢,甚至总是超时
镜像配好了,速度还是有瓶颈,就要分情况排查。先确认是否为某个大体积依赖首次下载,首次下载无法避免全量传输,速度再快也有物理上限。
接着检查镜像站本身是不是高峰期拥堵。公共镜像在技术社区集中讨论的热门版本发布日,经常有一波明显的下载高峰,这时候换一个备用源试试,能快速定位是不是源的问题。
还有一个细节:本机缓存目录可能已经存在损坏的半成品文件,比如上次下载中断留下的.part文件,这会导致 Maven 反复校验失败。排查思路是,找到~/.m2/repository下对应依赖的目录,删掉出问题的那一组目录,重新下载。
6.4 配了 repositories 却总感觉 mirror 在“抢”
如果项目 POM 里声明了多个 repository,而全局 mirror 用了external:*之类的宽匹配,就会把 POM 里手动声明的仓库也镜像到公共源。这在某些场景下是好事,在有些场景下是灾难。
我遇到过一种情况:项目 POM 里声明了一个第三方仓库,地址在国外,但访问极不稳定,配了external:*之后把请求全部指向国内镜像,反而变稳定了。而另一个场景下,项目依赖的某个构件只有 POM 声明仓库里有,镜像站根本不同步,这时候宽匹配就导致依赖拉不到。所以 mirrorOf 的宽度要根据项目的实际依赖来源来决定,不是越大越好。
7. 写在后头的经验之谈
Mirror 配置本身不难,真正难的是理解 Maven 处理仓库请求的顺序和匹配逻辑。我自己最早配置时就是抄了一段mirrorOf为*的配置,结果本地开发没问题,一接公司项目就翻车,私服的包全部拉不到。被这个问题折腾过之后,我才开始仔细理清 mirror、repository、profile 三者的区别,后面再也没在这类问题上踩过坑。
如果你只想要一个稳妥的起步方案,我的建议是一个公共镜像加mirrorOf=central就能覆盖大半需求;等以后有内网私有构件、多环境切换这类需求时,再把 profile 和排除写法用起来,一步一步演进,比一开始就堆一堆配置更不容易出错。配置文件改完先跑help:effective-settings确认效果,这个习惯能帮你过滤掉一大半配置不生效的问题,值得坚持。