简介:面向Java后端开发者与Maven使用者的阿里云镜像settings.xml配置包,针对默认中央仓库依赖下载缓慢、构建等待时间偏长的问题,提供可直接替换使用的本地配置文件,适合个人开发环境和团队项目快速接入镜像加速。压缩包内仅含1个xml文件,体积约711B,主体为Maven的settings.xml全局配置文件,围绕mirrors节点给出阿里云公共仓库的镜像声明,并涉及本地仓库、镜像覆盖范围等常见配置项,便于直接用于开发环境的依赖加速。已有4389人学习下载,说明该配置在实际开发中使用频率较高。使用者可获得一份开箱即用的镜像配置底稿,省去逐项编写镜像节点的成本,并便于对照理解id、url、mirrorOf等字段的含义;在团队协作中也可作为统一构建环境的基础模板,再按私有库访问需要补充servers与profiles配置。
1. 换完配置文件先别急着敲 package
接手一个老项目,第一次执行mvn clean install卡在 downloading 上十几分钟,日志里一路从国外站点往回拉 jar,这几乎是每个换过机器的人都碰过的事。最省事的处理方式,是把一份已经写好 maven 阿里云镜像的 settings.xml 直接覆盖到用户目录,不动 pom、不改 CI 脚本、不逐个仓库去排查,一个文件落在~/.m2/下,整台机器上的构建就都改走国内源了。
这份配置资源的定位很明确:一份可直接替换使用的 settings.xml,把 mirrors、profiles、repositories、pluginRepositories 四块都写全,替换掉本机原有的用户级配置即可生效。它适合三类人——刚换开发机、不想再等半天下载依赖的;要在流水线里固化下载源、不希望每个仓库都靠pom.xml各自为政的;以及手上握着一批历史项目、仓库地址写得五花八门,想从上游统一收口的维护者。
替换动作本身只有几秒钟,麻烦全在替换之后:文件明明覆盖了依赖还是走外网、mirrorOf写成通配符把公司内部仓库一并吞掉、IDEA 和命令行读到的根本不是同一份文件。下面按“放哪里、怎么写、边界在哪、出问题查什么”的顺序走一遍,能照着抄的地方我会把整段配置贴出来。
2. 三份 settings.xml 谁在做主:位置、合并顺序与优先级
先把“配置到底从哪来”这件事定死。Maven 启动时会按固定顺序找 settings 文件,找到一份或两份后做合并,最终生效的是一份叠加结果,而不是你眼前看到的那一个文件。很多人改了半天没反应,问题就出在这一步。
Maven 的查找顺序是:先用命令行-s显式指定的路径;没有指定就找${user.home}/.m2/settings.xml;这个文件不存在,才回落到${maven.home}/conf/settings.xml。注意第二和第三个位置并不是二选一——两份都存在时,Maven 会把它们合并,用户级的配置覆盖全局级的同名项。也就是说,全局那份里写了 mirror,用户级里也写了 mirror,最终生效的是用户级那一份。
2.1 全局配置与用户配置各管什么
${maven.home}/conf/settings.xml属于安装目录,跟着 Maven 版本走。换一次 Maven 版本、或者 IDE 内置了一套独立的 Maven,这个目录就变了,改动会跟着丢失。它的合理用途是放一些“这台机器上所有用户都该遵守”的东西,比如统一的内网制品库地址,但实际项目里很少有人这么干。
${user.home}/.m2/settings.xml是用户级配置,跟人走、不跟工具走,是绝大多数人该动的那一份。把它当作唯一的改动点,好处是无论命令行 Maven、IDEA 内置 Maven、CI 容器里挂载的 Maven,只要 HOME 指向同一个目录,读到的就是同一份配置。
我一般会在替换之前先做一次备份,把原始文件改成.bak后缀留在同目录:
# 先备份,出问题能一条命令退回去 cp ~/.m2/settings.xml ~/.m2/settings.xml.bak.$(date +%Y%m%d) # 确认用户级目录里确实存在这个文件,而不是只有全局那份 ls -l ~/.m2/settings.xmlWindows 下对应的是%USERPROFILE%\.m2\settings.xml。这里有个容易翻车的点:某些公司镜像过的开发机把USERPROFILE或者HOME重定向到了网络盘,~展开出来并不是你以为的目录,echo $HOME先看一眼再动手,比事后对日志划算得多。
2.2 pom.xml 里的仓库为什么会被 mirror 顶掉
pom.xml里的<repositories>和 settings.xml 里的<mirrors>是两层东西,很多人把它们当成同一种配置来理解,于是出现了“我 pom 里明明写了某个仓库,为什么下载还是走 mirror”的困惑。
规则很简单:Maven 解析一个仓库之前,先拿这个仓库的 id 和 url 去和所有 mirror 的mirrorOf规则做匹配,一旦命中,就把这个仓库的 url 整个替换成 mirror 的 url,仓库本身的名字和认证信息保留。命中之后,原来那个仓库地址就再也不会被访问了,所有请求都指向镜像。
这解释了两类现象。第一类,pom 里写了一个小众仓库,但 mirror 的mirrorOf是*,请求全被劫持到国内镜像,镜像里没有这个坐标,于是 404。第二类反过来,pom 里什么都没写,但依赖照样能拉下来,因为 Maven 内置了一个 id 为central的超级仓库,镜像把它替换掉了。
2.3 三个动作确认当前生效的文件
改完之后不要靠猜,让 Maven 自己把合并结果打出来。
# 1. 看当前用的 Maven 装在哪个目录,版本是多少 mvn -v # 2. 打印合并后的完整 settings,重点看 <mirrors> 和 <activeProfiles> 两段 mvn help:effective-settings # 3. 用指定文件跑一次,把本机其它配置的干扰排除掉 mvn -s /path/to/settings.xml help:effective-settings第二条命令输出的是合并之后的最终结果,但它不会告诉你某个 mirror 来自哪个文件。要定位来源,把用户级 settings.xml 临时改成别的名字,再跑一次对比;两次输出有差异的段落,就是用户级贡献的部分。这个二分法在排查“改了没生效”时比看日志快得多。
2.4 IDEA 和命令行读到的可能不是同一份
IDEA 的 Maven 面板里有三个独立输入框:Maven home path、User settings file、Local repository。只有勾上对应行的 Override 复选框,IDEA 才会用你填的路径;不勾就走它自己的默认值。命令行mvn读的是系统 HOME 下的配置,IDEA 读的是它自己解析出来的路径,两边不一致的时候,就会出现“命令行能拉、IDEA 拉不动”或者反过来的情况。
排查顺序是先看 IDEA 里 User settings file 那一行显示的实际路径,再和mvn help:effective-settings输出的内容对一下<localRepository>是否一致。不一致就统一到一个路径上,改完点一次 Reload All Maven Projects,别指望它自己刷新。
3. mirror 五个字段逐个拆:一份能直接覆盖的完整配置
搞清楚生效顺序之后,配置本身其实没几行。一个 mirror 块只有五个字段,真正需要动脑的只有url和mirrorOf两个,其余三个填对格式就行。
3.1 mirror 标签里每个字段的作用
| 字段 | 是否必填 | 作用 | 常见写法 |
|---|---|---|---|
id | 必填 | 唯一标识,出现在下载日志和_remote.repositories里 | aliyun-public |
name | 可选 | 可读名称,只在某些报错信息里出现 | Aliyun Public Mirror |
url | 必填 | 镜像仓库根地址,必须是完整可访问的 URL | https://maven.aliyun.com/repository/public |
mirrorOf | 必填 | 匹配规则,决定哪些仓库被替换 | central或*,!internal |
blocked | 可选 | 设为 true 时该镜像被禁用,Maven 3.8+ 用于拦截 http | 一般不写 |
id不是用来匹配的,匹配全看mirrorOf。但id会出现在下载日志的Downloading from aliyun-public:前缀里,也会写进本地仓库每个 artifact 目录下的_remote.repositories文件,所以起名要能一眼看出是哪个源,别用mirror1、test这种。
url必须是 https。从 Maven 3.8.1 开始,默认带了一条maven-default-http-blocker规则,把所有 http 仓库拦掉,报错形如Blocked mirror for repositories。老项目里抄来的 http 地址会直接触发这一条,替换成 https 就好。
3.2 一份能直接覆盖的完整配置
下面这份是可以整段替换进~/.m2/settings.xml的骨架,删掉了注释也能直接用。它做三件事:把 central 指到阿里云 public 聚合仓、通过 profile 补几个专用仓库组、把本地仓库位置显式写死避免被环境变量带偏。
<?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"> <!-- 本地仓库位置写死,避免 HOME 被改之后到处找缓存 --> <localRepository>${user.home}/.m2/repository</localRepository> <mirrors> <!-- 只接管 central,其它仓库按原样访问 --> <mirror> <id>aliyun-public</id> <name>Aliyun Public Mirror</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> <profiles> <profile> <id>aliyun-repos</id> <repositories> <repository> <id>aliyun-spring</id> <url>https://maven.aliyun.com/repository/spring</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </repository> <repository> <id>aliyun-google</id> <url>https://maven.aliyun.com/repository/google</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>aliyun-plugin</id> <url>https://maven.aliyun.com/repository/public</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <activeProfile>aliyun-repos</activeProfile> </activeProfiles> </settings>几个参数值得单独说。《localRepository》写成${user.home}而不是绝对路径,是为了让同一份文件在 Windows 和 Linux 上都能用;但如果你的 HOME 被重定向过,反而应该在这里写绝对路径,把它钉死。《snapshots》统一关掉,是因为公共镜像仓基本不托管 SNAPSHOT,开着只会让每次构建多一轮无意义的探测请求,还平白多出几条 404 日志。
《pluginRepositories》这一块最容易被漏。很多人只配了repositories,结果依赖能拉、插件拉不动,mvn报某个 plugin 找不到,日志里那条请求打的还是国外地址。插件解析走的是另一条路径,必须单独声明。
3.3 仓库组的选择:public 还是分仓库
阿里云镜像下面挂了好几个仓库组,地址后缀不同,内容也不同。选错的表现是“坐标明明存在,就是 404”。
| 地址后缀 | 覆盖内容 | 适合场景 |
|---|---|---|
public | 中央仓与其它常用仓的聚合 | 绝大多数项目,首选 |
central | 仅中央仓库 | 想严格控制来源、自己叠加其它仓 |
spring | Spring 相关发布件 | Spring Boot / Spring Cloud 项目 |
spring-plugin | Spring 生态插件 | 插件解析单独指向 |
google | Google 托管的一批坐标 | Android 相关、Guava 等 |
gradle-plugin | Gradle 插件依赖 | 混合构建的项目 |
apache-snapshots | Apache 快照件 | 少数追快照的场景 |
我的习惯是mirrorOf只写central,其余仓库组用 profile 显式补。这样每个仓库的来源都是清楚的,出问题时看日志前缀就知道命中了哪一条,而不用去猜*到底替换了什么。代价是配置长一点,但换来的是可排查性。
如果确实想用一条*省事,那至少要清楚它会带来的后果:所有仓库都被替换成同一个 url,包括你自己在 pom 里写的、或者 profile 里补的那些。后面第 4 章会专门讲这件事。
4. mirrorOf 的匹配边界:通配符会把内部仓库一起吞掉
mirrorOf是整套配置里最容易写错、也最难自查的一个字段。它支持五种写法,语义差别很大,抄配置的时候如果只抄了个*而不理解它匹配的范围,后面一定会踩坑。
4.1 匹配语法的五种形态
*:匹配所有仓库,包括 pom 里声明的、profile 里补的、Maven 内置的 central。external:*:匹配所有不在本机(localhost、file 协议)的仓库,排除本地文件仓库。external:http:*:只匹配 http 协议的远程仓库,用来单独拦截不安全的源。repo-a,repo-b:按仓库 id 精确匹配,逗号分隔。*,!repo-a:排除语法,!前缀表示不匹配该 id。
这里的 id 指的是仓库自己的 id,也就是 pom 里<repository><id>或者 settings 里 profile 下<repository><id>写的那个字符串,大小写敏感,多一个空格都算不同。
真实项目里最典型的翻车场景是这样的:公司内部有一个制品库,id 叫company-internal,pom 或者父 pom 里声明了它。开发者为了省事,把 mirror 的mirrorOf写成*,于是所有对company-internal的请求都被替换成了阿里云 public 的地址,内部包全部 404。日志里看到的是Could not find artifact com.xxx:internal-sdk:jar:1.0.0 in aliyun-public,很容易误判成“镜像里没有这个包”,实际上是请求根本不该发到镜像去。
4.2 排除内部仓库的两种写法
第一种是精确排除,把内部仓库的 id 加进!列表:
<mirror> <id>aliyun-public</id> <url>https://maven.aliyun.com/repository/public</url> <!-- 除了 company-internal,其余全走镜像 --> <mirrorOf>*,!company-internal</mirrorOf> </mirror>第二种是先缩小匹配范围,只接管外网仓库:
<mirror> <id>aliyun-public</id> <url>https://maven.aliyun.com/repository/public</url> <!-- 只接管外部的 http/https 仓库,本地和内网的不动 --> <mirrorOf>external:*,!company-internal</mirrorOf> </mirror>第二种写法的好处是它对“将来新增的内网仓库”更宽容一些——只要那类仓库走的是 file 协议或者 localhost,就天然不会被卷进来。但external:*依然会匹配内网的 http 地址,所以!排除项该写还得写,不能指望通配符自己分辨内网外网。
提示:排除项里的 id 必须和仓库声明处的 id 逐字符一致。改完之后跑
mvn help:effective-settings,在输出的<mirrors>段里确认mirrorOf那一行的内容,别凭记忆。
4.3 用 profile 补齐镜像里没有的坐标
镜像不是万能的,公共镜像仓偶尔会缺某些冷门坐标,尤其是刚发布不久或者发布在非主流仓库的包。这时候不要急着把mirrorOf改回*,而是在 profile 里补一条指向原始仓库的记录。
<profile> <id>extra-repos</id> <repositories> <repository> <id>company-internal</id> <url>https://repo.example-internal.invalid/repository/maven-public/</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>true</enabled></snapshots> </repository> </repositories> </profile> <activeProfiles> <activeProfile>extra-repos</activeProfile> </activeProfiles>这段配置挂上去之后,只要mirrorOf里带了!company-internal,这个仓库就会按原地址被直接访问,其它仓库继续走镜像。两套机制配合起来,才叫“既用上了镜像,又没丢掉该走原路的那些”。
注意 SNAPSHOT 的开关。内部仓库往往托管着项目自己的快照件,这里snapshots要开;而镜像那边的仓库组对快照基本不提供服务,开了也是白开,所以在 mirror 对应的那些仓库里统一关掉,能少掉一批超时等待。
5. 依赖拉不动时的排查顺序:五条现场记录
配置写对了不代表一次就能过。下面这几条是替换 settings.xml 之后最常出现的现象,按“看到什么、为什么、怎么办”记录,遇到时按顺序对号入座。
5.1 配置换了但下载还是走外网
现象:mvn -X日志里的Downloading from后面跟的还是境外域名,或者help:effective-settings输出的 mirrors 段是空的。
原因通常是改错了文件。要么改的是${maven.home}/conf/settings.xml而用户级那份存在并且优先级更高,要么 IDEA 勾了 Override 指向了另一个路径。还有一种情况是 XML 头部声明了错误的命名空间,Maven 解析时把整段<mirrors>忽略掉了,不报错也不生效。
解决:先mvn -v确认 Maven home,再mvn help:effective-settings确认合并结果里有没有你的 mirror。两处都对不上,就沿着 2.4 节的思路去查 IDEA 的 User settings file 路径。命名空间用 3.2 节那份骨架里的原样,不要自己改。
5.2 内部包突然全部 404
现象:原本能拉的公司内部 artifact 开始报Could not find artifact,日志前缀显示请求打到了镜像地址。
原因:mirrorOf用了*,把内部仓库一起替换了。镜像里当然没有这些坐标。
解决:在mirrorOf后面加!内部仓库id,确认 id 拼写和仓库声明处一致。如果项目里有多个内部仓库,逐个加进去,别图省事只加一个。
5.3 同一个依赖反复失败,日志里全是 lastUpdated
现象:某个坐标每次都重新尝试下载,每次都失败,本地仓库对应目录下能看到*.lastUpdated文件,时间戳是刚才。
原因:Maven 在下载失败后会写一个 lastUpdated 标记,默认策略下在一定时间窗口内不再重试。改完网络或配置之后,这些旧标记还在,导致它继续沿用失败结论。
解决:强制更新一次,同时把陈旧的标记清掉。
# -U 强制检查更新,忽略本地缓存的失败标记 mvn -U clean install # 彻底清理:删掉所有 lastUpdated 标记后再构建 find ~/.m2/repository -name "*.lastUpdated" -deleteWindows 下用for /r %i in (*.lastUpdated) do del /q "%i"。清理前确认没有其它构建在跑,避免删到别人正在写的文件。
5.4 启动就报 Non-parseable settings
现象:任何mvn命令都直接失败,提示 POM/settings 无法解析,位置指向 settings.xml 的第几行。
原因:XML 本身不合法。常见的是从网页复制配置时带进了全角字符、智能引号,或者文件开头多了 BOM,又或者<mirrors>和<profiles>的标签没闭合。
解决:用xmllint --noout ~/.m2/settings.xml先做语法校验,报错行号比 Maven 的提示更准。确认文件编码是 UTF-8 无 BOM,编辑器里存一次“UTF-8 without BOM”即可。
5.5 报 Blocked mirror for repositories
现象:日志里出现Blocked mirror for repositories,后面跟着某个 http 地址和default, default。
原因:Maven 3.8.1 之后内置了maven-default-http-blocker,所有 http 协议的仓库默认被拦。老项目从别处抄来的配置里常常还留着 http 地址。
解决:把 url 换成 https 版本。如果对方确实只提供 http,就显式声明一个blocked为 false 的 mirror 放行,但更推荐的做法是直接把那个仓库从配置里删掉,用镜像替代。
注意:五条里最容易误判的是 5.3。看到“下载失败”第一反应往往是网络不通,实际上多数时候是本地缓存里的失败标记在起作用,先清理再判断网络。
6. 把替换动作变成可验证的流程:三条校验命令与一个清理脚本
替换完 settings.xml 之后,我一般不看构建结果,先看三样东西,三样都对上才动手跑项目。
第一样是合并后的配置。mvn help:effective-settings的输出里,<mirrors>段应该只有你写的那几条,<activeProfiles>里应该有你在 profile 里声明的 id。如果 mirrors 是空的,说明文件没被读到,或者 XML 结构被破坏,这时候继续往下跑没有意义。
第二样是实际请求地址。随便挑一个还没下载过的坐标,用 debug 模式拉一次,看日志前缀:
# -X 打印详细日志,grep 出实际的仓库地址 mvn -X dependency:get -Dartifact=org.apache.commons:commons-lang3:3.14.0 2>&1 | grep -i "downloading from"正常情况下应该看到Downloading from aliyun-public: https://maven.aliyun.com/repository/public/...。如果前缀是别的 id,说明命中的不是你写的 mirror。
第三样是本地仓库里的来源标记。每个下载成功的 artifact 目录下都有一个_remote.repositories文件,里面记录了它来自哪个仓库 id:
# 看某个依赖实际来自哪个仓库 cat ~/.m2/repository/org/apache/commons/commons-lang3/3.14.0/_remote.repositories内容是commons-lang3-3.14.0.jar>aliyun-public=这样的行,等号左边的 id 就是实际命中的仓库。这个文件比日志更可靠,因为它记录的是落盘那一刻的事实,不受日志级别影响。切换镜像之后如果发现某个依赖一直没更新,先看这里——如果 id 还是旧的,说明本地缓存没被替换,删掉对应目录重拉最干脆。
最后附一个日常清理脚本,我放在~/bin/mvn-cleanup.sh里,换机器或者怀疑缓存出问题时跑一次:
#!/usr/bin/env bash set -euo pipefail REPO="${HOME}/.m2/repository" # 1. 清掉下载失败的标记文件 find "${REPO}" -name "*.lastUpdated" -delete # 2. 清掉下载中断留下的临时文件 find "${REPO}" -name "*.part" -delete find "${REPO}" -name "*.tmp" -delete # 3. 把来源标记里不含目标镜像 id 的目录列出来,人工确认后再决定删不删 grep -rl "aliyun-public" "${REPO}" --include="_remote.repositories" | wc -l脚本的第三步只统计不删除,是有意的。整目录删除会连带把其它仓库来源的包一起清掉,而有些包恰恰只有内部仓库才有,删了就得重新走一遍内部网络。统计出来之后先看一眼数量,正常情况下应该是越来越大;如果长期不增长,说明新依赖根本没走镜像,回到第 5 章从头查。
从那以后我每次换机器、换 Maven 版本、或者改完 settings.xml,都会强制跑一遍help:effective-settings加一次dependency:get验证,两分钟的事,能省掉后面半天的误判。希望帮到你。
本文还有配套的精品资源,点击获取