适用读者:JDK 已对齐(S01-B02),第一次跑
mvn就撞上Could not resolve dependencies或各种插件报错的人。
前置:S01-B02。
实测环境:Ubuntu 24.04.1 LTS / OpenJDK 17.0.20.1 / Apache Maven 3.8.7 / 实测日期 2026-11-05。
版本基线:Maven 3.9.x(3.8.7 为实测版本,差异处单独标注);maven-compiler-plugin3.13.0。
覆盖旧文:A-1398(Maven 使用说明与配置,101 点)、A-1374(IDEA 配置 Maven,71 点)。
新增扩展:E-S01-004(Maven 3.9/4.x 与 mvnw)、E-S01-005(settings.xml与mirrorOf)、E-S01-006(.mvn/maven.config)。
上一篇:S01-B02 · JDK 安装与多版本共存:JAVA_HOME 到底该指向谁
下一篇:S01-B04 · IDEA 基本功。
1. 一句话结论
Maven 就三件事:用「坐标」找到 jar,用「生命周期」把源码变成产物,用「本地仓库 + 仓库链」缓存一切。 依赖问题的 90% 出在"找 jar"这一环——而找 jar 的顺序是固定的,背下来就能自己排障。
mvn package
│
▼
① 读 pom.xml(坐标 + 依赖 + 插件)
│
▼
② 解析依赖 → 查 本地仓库 ~/.m2/repository
│ │
│ 命中 ────┴──► 直接用
│ 未命中 ──► ③ 远程仓库链
▼ │
④ 执行生命周期 ├─ 镜像(mirror,会"劫持"仓库)
validate→compile→test └─ 中央仓库 / 私服
→package→verify→install→deploy
2. 三个概念,先分清
| 概念 | 是什么 | 例子 |
|---|---|---|
| 坐标(GAV) | 一个 jar 的唯一定位:groupId:artifactId:version |
org.apache.commons:commons-lang3:3.14.0 |
| 生命周期(lifecycle) | 阶段序列,跑后面的阶段会自动跑前面的 | compile → test → package → install → deploy |
| 仓库(repository) | 存放 jar 的地方。本地一份,远程若干份 | ~/.m2/repository、repo.maven.apache.org、公司 Nexus |
记住一个反直觉点:mvn package 不等于"只打包"。它会把 validate → compile → test → package 全跑一遍。想跳过测试:-DskipTests(编译测试但不跑)或 -Dmaven.test.skip=true(连测试编译都不做)。
3. 装 Maven:解压即用,但要配环境变量
Maven 是绿色包,下载解压即可(旧文 A-1398 说得对:它"就是文件,不需要安装")。
# Linux / macOS
curl -O https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz
sudo tar -xzf apache-maven-3.9.9-bin.tar.gz -C /opt
export PATH=/opt/apache-maven-3.9.9/bin:$PATH # 写进 ~/.bashrc 或 ~/.zshrc
mvn -v
:: Windows:解压到 D:\tools\apache-maven-3.9.9
:: 新增 MAVEN_HOME=D:\tools\apache-maven-3.9.9
:: Path 里追加 %MAVEN_HOME%\bin
mvn -v
验证要看的不是"Maven 3.9.9"这一行,而是第三行的 Java version:
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64 ← 这一行
它和 java -version 不一致,说明 §6(B02)的三处对齐没做好。
4. 实测:从空本地仓库到第一个 jar
4.1 最小可运行项目
/tmp/b03
├── pom.xml
└── src/main/java/demo/App.java
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>s01-b03</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
</dependencies>
</project>
App.java:
package demo;
import org.apache.commons.lang3.StringUtils;
public class App {
public static void main(String[] args) {
System.out.println("blank? " + StringUtils.isBlank(" "));
}
}
4.2 第一次构建:报错了,而且报得很有教育意义
mvn -B -Dmaven.repo.local=/tmp/b03/repo package
实测输出(截取):
[INFO] BUILD FAILURE
[INFO] Total time: 22.373 s
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
(default-compile) on project s01-b03: Compilation failure:
[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
注意三个细节:
maven-compiler-plugin:3.1—— 这是 Maven 内置的"默认插件版本",非常老。- 我明明写了
<maven.compiler.release>17</maven.compiler.release>,它却说"Source option 5"——因为release参数是 compiler 插件 3.6+ 才有的,3.1 不认这个属性,于是回落到默认的 1.5。 - 它连
javac都没调通就失败了,跟 JDK 无关、跟网络无关——这类报错最容易被误判成"环境没装好"。
4.3 修法:锁插件版本(这是 Maven 项目的第一条纪律)
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration><release>17</release></configuration>
</plugin>
</plugins>
</build>
实测结果:
[INFO] Building jar: /tmp/b03/target/s01-b03-1.0.0.jar
[INFO] BUILD SUCCESS
[INFO] Total time: 14.123 s
| 指标 | 数值(实测) |
|---|---|
| Maven 自报总耗时 | 14.1 s |
| 墙钟耗时 | 16.5 s |
| 本地仓库体积(从 0 开始) | 14 MB |
| 产物 jar | 2.2 KB |
为什么必须显式锁插件版本? Maven 的默认插件版本跟随 Maven 大版本,跨版本会变;一旦换机器/换 Maven,构建行为就可能悄悄变化。锁版本 = 构建可复现。
对 B02 的修正:上一篇文章我说"
maven.compiler.release是最佳写法",实测证明只写属性不够,必须同时锁 compiler 插件版本(≥3.6)。B02 已同步更新这一处。
5. 读懂依赖树:dependency:tree
mvn -B dependency:tree
实测输出:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ s01-b03 ---
[INFO] demo:s01-b03:jar:1.0.0
[INFO] \- org.apache.commons:commons-lang3:jar:3.14.0:compile
读法:
| 符号 | 含义 |
|---|---|
\- / +- / ` |
` |
行尾 :compile / :test / :runtime / :provided |
scope,决定这个 jar 在哪些阶段可见、是否进产物 |
omitted for duplicate |
同名不同版本的依赖被"调解"掉了(进阶见 S01-J07) |
四个 scope 的实用判据:
| scope | 编译期 | 运行期 | 进 jar/war | 典型用途 |
|---|---|---|---|---|
compile(默认) |
✅ | ✅ | ✅ | 业务依赖 |
provided |
✅ | ❌(容器提供) | ❌ | servlet-api、lombok |
runtime |
❌ | ✅ | ✅ | JDBC 驱动 |
test |
仅测试 | 仅测试 | ❌ | JUnit、Mockito |
6. 三个开关:-o、-U、-X
6.1 -o(离线)——最能暴露真相的模式
mvn -B -o package # 只允许用本地仓库
实测报错(换了未下载过的版本号):
[ERROR] Failed to execute goal on project s01-b03-offline: Could not resolve dependencies
for project demo:s01-b03-offline:jar:1.0.0:
Cannot access central (https://repo.maven.apache.org/maven2) in offline mode
and the artifact org.apache.commons:commons-lang3:jar:3.13.0 has not been downloaded from it before.
这句报错值得背下来:它明确告诉你"哪个仓库、哪个坐标、为什么失败"。当别人说"我这儿能编译你那儿不能",第一件事就是让他 -o 跑一次——能把"网络问题"和"代码问题"一刀切开。
6.2 -U(强制更新快照)
依赖是 SNAPSHOT 版本、或怀疑本地缓存脏了时用。日常别加,会变慢。
6.3 -X(debug)
排查插件行为、看真实生效的 settings.xml 时用。输出很长,配合 grep 用。
mvn -X package 2>&1 | grep -E "Local repository|Using mirror|Reading global settings"
7. 镜像(mirror):最容易配错的一处
7.1 配置放在哪,谁覆盖谁
| 位置 | 作用域 | 优先级 |
|---|---|---|
~/.m2/settings.xml |
当前用户 | 高(覆盖全局) |
$MAVEN_HOME/conf/settings.xml |
全机 | 低 |
pom.xml 的 <repositories> |
单项目 | 最低(且会被 mirror "劫持",见下) |
7.2 一个可用的镜像配置
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<mirrors>
<mirror>
<id>aliyun</id>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
7.3 实测三组对照(同一台机器,空仓库,同一坐标)
| 组 | 配置 | 实测耗时 | 结果 |
|---|---|---|---|
| A | 默认(官方 Central) | 38.4 s | 成功 |
| B | 阿里云镜像 mirrorOf=central |
75.5 s | 成功 |
| C | 故意写坏 mirrorOf=* 指向不存在域名 |
— | 失败,见下 |
别照抄"国内镜像一定更快"。同一条坐标,从我这台机器(海外网络)走官方 Central 用了 38 s,走阿里云用了 75 s。镜像快不快取决于你到镜像的网络,不取决于镜像的名气。 判断方法就是自己计时跑一次
dependency:get。
C 组的报错(很有代表性):
[ERROR] No plugin found for prefix 'dependency' in the current project and in the plugin groups
[org.apache.maven.plugins, org.codehaus.mojo] available from the repositories
[local (/tmp/mirrors/r3), broken (https://repo.example.invalid/maven2)]
读法:mirrorOf=* 把所有仓库(包括插件仓库)都指向了那个坏地址,于是连插件都找不到。这句话里 available from the repositories [...] 就是当时真实可用的仓库清单——排障时先看这一句。
7.4 两个常见配错
| 错误配置 | 后果 | 正确做法 |
|---|---|---|
mirrorOf=* 指向公网镜像 |
私服被绕过,公司内部构件永远拉不到 | 用 mirrorOf=central 或 mirrorOf=*,!internal(排除私服) |
同时配 3 个镜像且都写 mirrorOf=central |
只有第一个生效,其余静默忽略 | 一个仓库只留一个镜像,写清 <name> |
8. 可复现构建:mvnw 与 .mvn/maven.config
8.1 wrapper:把 Maven 版本钉在仓库里
mvn -N wrapper:wrapper -Dmaven=3.9.9
实测生成:
mvnw # Unix 脚本
mvnw.cmd # Windows 脚本
.mvn/wrapper/maven-wrapper.properties # 记录 distributionUrl
之后团队统一用 ./mvnw clean package:每个人用的 Maven 版本一致,不再出现"我这里能编译"。
8.2 .mvn/maven.config:把常用参数固化
# .mvn/maven.config(每行一个参数)
-Dmaven.repo.local=/data/m2
--offline
实测生效(mvn -X 里能看到这些参数被合并进命令行)。适合放:统一本地仓库路径、统一 -T 并行度、CI 上的 -B。
注意:
maven.config里放--offline是危险操作——会让所有需要新依赖的构建失败。它适合"验证可复现性"的临时场景,不适合长期提交。
9. 排障决策树:Could not resolve dependencies
报错 → 先看最后一行的 "Cannot access <repo>" / "has not been downloaded" / "Could not find artifact"
│
├─ 提到 in offline mode ──────────► 去掉 -o,或确认该依赖是否真的被下载过
├─ 提到 Could not find artifact ─► 坐标写错(拼写/版本不存在)→ mvnrepository.com 核对
├─ 提到 Connection timed out / UnknownHost ─► 网络或镜像问题 → 用 §7.3 的方法计时验证
├─ 提到 401/403 ─────────────────► 私服认证(settings.xml 的 <servers>)
└─ 什么都没提到,但 jar 明明在本地 ─► 本地仓库损坏 → 删掉该坐标目录重下;仍不行 -U
配合三条命令定位:
mvn -o package # 分离"网络 / 代码"
mvn -U package # 强制刷新
mvn -X package | grep -E "Local repository|Using mirror" # 看实际生效的配置
10. 自检清单
| # | 检查 | 命令 | 预期 |
|---|---|---|---|
| 1 | Maven 与 JDK 一致 | mvn -v |
第三行 Java version 与 java -version 一致 |
| 2 | 能离线自证 | mvn -o -q package(已构建过的项目) |
成功;失败则说明依赖没落本地 |
| 3 | 插件被锁定 | mvn help:effective-pom | grep -A2 compiler-plugin |
有明确 <version> |
| 4 | 依赖树可读 | mvn dependency:tree |
能看到直接依赖与传递依赖分层 |
| 5 | 产物体积合理 | ls -lh target/*.jar |
普通业务 jar 几 KB~几十 MB;异常巨大通常是打包了不该打的依赖 |
| 6 | 环境可复现 | ./mvnw -v |
版本等于 .mvn/wrapper/maven-wrapper.properties 里声明的版本 |
11. 旧文对照
| 项 | 旧文 | 本篇处理 |
|---|---|---|
| Maven 是什么 | A-1398:核心是"帮你管理 jar 包" | 保留结论,补"坐标 / 生命周期 / 仓库链"三层模型 |
| 下载与安装 | A-1398/A-1374:解压 + 配环境变量 | 保留;补三平台命令与 mvn -v 读法 |
| 本地仓库改路径 | A-1374:在 Maven 目录下建 repository |
保留,改为"用 settings.xml 的 <localRepository> 或 -Dmaven.repo.local"(更规范,且支持多项目隔离) |
| 镜像配置 | A-1398:阿里云镜像 | 保留配置,但补 §7.3 实测:镜像不一定更快;补 mirrorOf 语义与私服被绕过的坑 |
| 默认插件版本 | 两者都未涉及 | 新增 §4.2 实测失败案例(Source option 5)与锁版本纪律 |
dependency:tree / -o -U -X |
未涉及 | 新增 §5、§6 |
mvnw / maven.config |
未涉及 | 新增 §8(E-S01-004、E-S01-006) |
| IDEA 里配 Maven | A-1374:项目级/全局 | 移入 B04 §工程集成,避免与 Maven 本体混讲 |
12. 自测
mvn -o package报has not been downloaded from it before,说明什么?该怎么修?- 为什么只写
<maven.compiler.release>还不够?缺的那一块是什么? - 公司有私服,
settings.xml里写了mirrorOf=*指向公网镜像,会发生什么? provided与compile的区别,用一个真实依赖举例说明(提示:lombok、servlet-api)。.mvn/maven.config里放--offline会带来什么风险?
13. 下一篇
S01-B04 · IDEA 基本功:界面、视图、外观与编码习惯
Maven 打通之后,回到 IDE:哪些设置属于"个人偏好"可以随便改,哪些属于"团队约定"必须统一?下一篇把旧文里 10 篇零散的设置教程(字体、主题、汉化、行号、Tab、导包、注释模板……)合成一篇可执行的配置流程,并用 .editorconfig 把"团队约定"落到仓库里。
本篇属于系列《S01 · 开发环境与工具链》。旧文来源:A-1398、A-1374;扩展点 E-S01-004/005/006。全部命令与耗时均为 2026-11-05 实测,仓库截面见 ../coverage.csv。