ARTICLE · 1304690
Maven 深入:构建、依赖与镜像下载流程
💡 导语:日常敲
mvn package时,你有没有想过:一个依赖 JAR 到底是从哪里下载下来的?配了镜像之后,Maven 访问的又是谁?这篇文章把 Maven 的坐标、生命周期、依赖管理和镜像匹配机制一次讲透.
日常开发中,Maven 项目通过一个根 pom.xml 管理多个模块:根 POM 的 packaging 为 pom,在 <modules> 中列出 spring-boot-configuration、websocket、http-clients 等模块;子模块通过 <parent> 继承根 POM 中的 Java 版本、编码和代码检查插件配置。
一、Maven 的核心概念
groupId:artifactId:version | com.example.learn:learn:1.0.0 | |
pom.xml 列出子模块并配置公共构建规则 | ||
learn | ||
<modules> 将多个模块放在一起构建 | mvn verify 可构建所有模块 | |
install |
定位构件时,除了 GAV,还可能用到 classifier 和 extension/type,例如测试 JAR 常带 classifier=tests。另外,不同仓库中的同一 GAV 也可能对应不同内容,排查依赖问题时要留意下载来源。
二、生命周期与常用命令
validate → compile → test → package → verify → install → deploy 上面是默认生命周期中的几个常用阶段。执行某个阶段时,Maven 会先执行它之前的所有阶段:例如运行 mvn package,会先校验、编译、测试,再打包;运行 mvn verify,则会在打包后继续执行验证。很多项目会在 validate 阶段检查代码格式和 import 排序,在 verify 阶段执行 PMD 检查。
mvn validate | |
mvn compile | |
mvn test | |
mvn package | target/ 目录 |
mvn verify | |
mvn install | |
mvn deploy | |
mvn dependency:tree -Dverbose | |
mvn help:effective-pom |
三、依赖管理与作用域
模块要使用某个依赖,需要在 <dependencies> 中声明。<dependencyManagement> 用来管理版本、scope 和排除项,本身不会引入依赖。多模块项目通常把公共依赖版本放在父 POM 的 dependencyManagement 或 BOM 中,各子模块再声明自己用到的依赖。
compile | ||
provided | ||
runtime | ||
test |
遇到版本冲突,可以先运行 mvn dependency:tree -Dverbose,看看不同版本分别由哪些依赖引入,再通过 BOM 或 dependencyManagement 统一版本,或用 <exclusions> 排除不需要的传递依赖。
删除 .m2 中的缓存并不会改变依赖关系,重新下载后仍会出现同样的冲突。冲突要在 POM 层面解决,而不是靠清缓存。
四、一个依赖 JAR 是如何下载的
以 org.springframework.boot:spring-boot-starter-web:3.3.4 为例:Maven 从项目 POM 和父 POM 中确定依赖坐标,再读取这个依赖的 POM,继续查找它依赖的其他构件。解析过程中需要用到各依赖的 POM、JAR,以及相关的校验文件和元数据;本地缺少的文件会从远程仓库下载。
完整流程如下图:

图中的本地仓库通常位于开发机的 .m2/repository。配置镜像后,下载地址变了,但下载的构件仍然保存在本地仓库,后续构建可以继续使用。
五、有镜像时,Maven 到底访问谁
镜像通常配置在 ~/.m2/settings.xml 的 <mirrors> 中,Windows 下的路径一般是 C:\Users\<用户名>\.m2\settings.xml。其中:
mirrorOf用来匹配原仓库的 ID <mirror>自身的 <id>只是这个镜像的名称仓库 URL 和依赖的 groupId、artifactId都不参与这项匹配
例如 Maven 默认的 Central 仓库 ID 是 central;项目 POM 中也可以定义其他仓库:
<repositories><repository><id>company-releases</id><url>https://packages.example.com/maven/releases</url></repository></repositories>Maven 准备访问这些仓库时,会分别用 central、company-releases 去匹配 mirrorOf。
5.1 匹配顺序
Maven 为每个待访问的仓库选择一个镜像,查找顺序如下:
- 优先找
mirrorOf与仓库 ID 完全相同的镜像。例如仓库 ID 为 central,则mirrorOf=central直接命中。 没有精确匹配时,再按 settings.xml中<mirrors>的声明顺序,查找第一个符合通配符、列表或排除规则的镜像。
选中后,Maven 直接请求镜像 URL。即使请求失败,也不会为同一个原仓库继续尝试其他镜像。
如果镜像使用 Nexus 或 Artifactory,能从它下载哪些依赖,还要看服务端配置了哪些仓库和上游代理。mirrorOf 只决定请求发往哪里。
5.2 mirrorOf 写法与匹配结果
mirrorOf | ||
|---|---|---|
central | central 的仓库 | |
company-releases | company-releases | |
central,company-releases | ||
* | ||
*,!company-releases | company-releases | |
external:* | localhost 且非 file: 的仓库 | |
external:http:* |
多个规则用英文逗号分隔,中间不要加空格。例如 *,!company-releases 会正确排除 company-releases;写成 *, !company-releases 后,空格会使排除规则无法按预期匹配。
5.3 具体配置举例
只替换 Central 时,指定它的仓库 ID:
<mirror><id>central-proxy</id><url>https://nexus.example.com/repository/maven-central/</url><mirrorOf>central</mirrorOf></mirror>这样,访问 central 的请求会发往 central-proxy,POM 中的 company-releases 不受影响。
所有仓库都通过公司私服访问,可以使用 *:
<mirror><id>company-public</id><url>https://nexus.example.com/repository/maven-public/</url><mirrorOf>*</mirrorOf></mirror>在这个配置下,Maven 会直接访问 company-public,不会先请求 Central。所需的 Central、企业库和第三方库构件,都需要由这个私服仓库组提供或代理。
内部发布库需要直连(如有独立权限或网络入口),可以将它排除:
<mirror><id>company-public</id><url>https://nexus.example.com/repository/maven-public/</url><mirrorOf>*,!company-releases</mirrorOf></mirror>此时,central 和第三方仓库等仍通过 company-public 访问,company-releases 则使用 POM 或 Profile 中声明的原始 URL。
给 Central 单独配镜像,其余仓库用另一个镜像:
<mirrors><mirror><id>central-only</id><url>https://nexus.example.com/repository/maven-central/</url><mirrorOf>central</mirrorOf></mirror><mirror><id>all-other-repos</id><url>https://nexus.example.com/repository/maven-public/</url><mirrorOf>*</mirrorOf></mirror></mirrors>central 会匹配 central-only,company-releases 等其他仓库会匹配 all-other-repos。精确匹配优先于通配符匹配;如果没有精确匹配,就按声明顺序使用第一个符合条件的规则。
5.4 如何确认实际命中哪个镜像
mvnhelp:effective-settings mvn-Xdependency:resolve 5.5 依赖不在 Central,而在其他仓库时
假设项目需要 com.vendor:payment-sdk:1.2.0,Central 中没有这个依赖,厂商提供的仓库地址是 https://repo.vendor.example/maven/releases/。可以在父 POM 中声明这个仓库让子模块继承,也可以把它配置在团队共用的 settings.xml Profile 中:
<repositories><repository><!-- 这是原始仓库 ID,供 mirrorOf 与 server 凭据匹配 --><id>vendor-releases</id><name>VendorReleaseRepository</name><url>https://repo.vendor.example/maven/releases/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories>依赖本身的声明方式不变:
<dependency><groupId>com.vendor</groupId><artifactId>payment-sdk</artifactId><version>1.2.0</version></dependency>如果厂商的 Release 和 Snapshot 使用不同的 URL,就分别配置两个仓库。上面的配置只启用了 Release;快照仓库则将 releases.enabled 设为 false、snapshots.enabled 设为 true。如果同一个 URL 同时提供两类构件,可以在一个仓库配置中同时启用两者:
<repository><id>vendor-all</id><url>https://repo.vendor.example/maven/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository>这两个 enabled 开关决定 Maven 是否向该地址查询 Release 或 Snapshot。开启查询后能否下载到构件,还要看服务端是否提供它。两类构件的更新策略也可以分别配置。
仓库需要认证时,在 settings.xml 的 <servers> 中配置凭据,server.id 要与实际访问的仓库 ID 一致。直连 vendor-releases 时可以这样写:
<servers><server><id>vendor-releases</id><username>${env.VENDOR_REPO_USERNAME}</username><password>${env.VENDOR_REPO_TOKEN}</password></server></servers>配置了 mirrorOf=* 时怎么办
如果 settings.xml 中配置了 <mirrorOf>*</mirrorOf>,新加的 vendor-releases 也会被匹配,请求会发往公司镜像。因此,只在 POM 中添加厂商仓库地址可能还不够,还需要处理镜像配置:
- 通过公司私服下载
:在 Nexus/Artifactory 中为厂商仓库创建代理,并加入 maven-public仓库组。Maven 访问company-public,对应的认证信息也配置在server.id=company-public下。 - 直接访问厂商仓库
:将镜像规则改为 <mirrorOf>*,!vendor-releases</mirrorOf>。Maven 会使用厂商的原始 URL,并读取server.id=vendor-releases的凭据。这种方式需要开发机和 CI 都能访问厂商仓库,且公司允许直连。
完整解析路径如下图:















































































