创建日期:2026-09-08 | 最近更新:2026-09-08 事实核对基于 Apache Maven 3.9.16(2026 当前稳定线;Maven 4.0.0 仍处 RC)在本机实测:从 Maven Central 下载 Maven 本体、用 JDK 11 实际编译打包并运行了示例;下方日志与输出均为真实运行结果。
Maven 入门:先搞懂坐标、仓库、生命周期
一句话:Maven 是 Java 世界事实标准的构建工具——它把「编译、测试、打包、依赖下载」这些事,收敛成一句
mvn package和一张声明式的pom.xml。你写「要什么」,它负责「去哪下、怎么编、打出什么」。为什么在这个码列讲它?因为它就是 Spring 入门 里那个
pom.xml和./mvnw背后的东西——看懂 Maven,才算真正看懂了 Spring Boot 工程的骨架。
1. Maven vs Gradle:为什么(这篇)选 Maven
先回答「Maven 还是 Gradle」:
| Maven | Gradle | |
|---|---|---|
| 配置 | XML(pom.xml),声明式、啰嗦但直白 | Groovy/Kotlin DSL,灵活、可编程 |
| 上手曲线 | 平缓,约定固定 | 灵活但容易写飞 |
| 构建速度 | 中(但现代版 + daemon 已不错) | 通常更快(增量 + 守护进程) |
| 生态默认 | Spring Boot / 大量老项目默认 | Android、新 Kotlin 项目常见 |
| 2026 状态 | 稳定线 3.9.16(4.0 仍 RC) | 8.x |
- 新 Java 服务端项目(尤其 Spring Boot)教程几乎全是 Maven +
mvnw,跟着走零摩擦; - Gradle 的「灵活」对新人反而是负担——本系列先用 Maven 把构建心智打通。
本机只有 JDK 11 + 没装 Maven,所以这篇演示是把 Maven 3.9.16 直接下载解压来跑的(Java 11 可运行 Maven;但编译目标只能到 Java 11,跑 Spring Boot 4 仍需 JDK 17+)。
2. 三个核心心智模型
① 坐标(Coordinate):Maven 世界里一个 jar 的「身份证」
任何构件都由三件事唯一定位,pom.xml 顶部就是它:
<groupId>com.example</groupId> <!-- 谁(公司/组织,通常反写域名) -->
<artifactId>greet</artifactId> <!-- 什么东西 -->
<version>0.1.0</version> <!-- 哪个版本 -->
这三件套拼起来就是它在仓库里的路径:com/example/greet/0.1.0/greet-0.1.0.jar。你声明「依赖某坐标」,Maven 就知道去哪找。
② 仓库(Repository):下载这件事是「分级缓存」
本地仓库 ~/.m2/repository ← Maven 先把依赖下到这,用完就缓存
↑ 缺了去远程拉
中央仓库 repo.maven.apache.org ← 全世界的公开 jar 都在这
↑ 也可以加公司私服 / 镜像
第一次构建某依赖会看到 Downloading…,之后就秒过——因为已躺在本地仓库。你可以 ls ~/.m2/repository/org/apache/commons/commons-lang3/3.20.0/ 亲眼看到它被缓存下来。
③ 生命周期(Lifecycle):mvn package 不是一件事,是一串事
默认生命周期是固定顺序的阶段(phase),mvn package 会从头按序执行:
validate → compile → test → package → verify → install
校验 编译 跑单测 打 jar 集成验收 装进本地仓库
而每个阶段背后的「实际干活者」是插件(plugin)——比如 compile 阶段绑定 maven-compiler-plugin、package 绑定 maven-jar-plugin。阶段是流程,插件是执行者,这个区分是理解一切 Maven 报错的关键。
3. 标准目录布局(约定优于配置)
my-app/
├─ pom.xml
└─ src/
├─ main/java/... # 业务代码
├─ main/resources/ # 配置文件(打进去)
├─ test/java/... # 单元测试
└─ test/resources/
不用告诉 Maven 代码在哪——它假定你遵守这个布局。这也是 Spring Boot 工程长那样的原因。
4. 动手实测:一个最小项目(本机跑通)
一个用第三方库 commons-lang3 把名字首字母大写的极简程序。完整 pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>greet</artifactId>
<version>0.1.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>11</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.20.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive><manifest><mainClass>com.example.Greet</mainClass></manifest></archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
// src/main/java/com/example/Greet.java
package com.example;
import org.apache.commons.lang3.StringUtils;
public class Greet {
public static void main(String[] args) {
String name = args.length > 0 ? args[0] : "world";
System.out.println("Hello, " + StringUtils.capitalize(name) + "!");
}
}
跑 mvn package(加了 -B 去掉彩色进度,日志清晰)。关键片段(真实输出):
[INFO] --- resources:3.3.1:resources ... --- # ① 复制 main/resources
[INFO] --- compiler:3.13.0:compile ... --- # ② 编译 main/java
[INFO] --- resources:3.3.1:testResources ... ---
[INFO] --- compiler:3.13.0:testCompile ... --- # ③ 编译测试
[INFO] --- surefire:3.2.5:test ... --- # ④ 跑单元测试
[INFO] --- jar:3.4.2:jar ... --- # ⑤ 打包 jar
[INFO] BUILD SUCCESS # 构建成功
看到那串 plugin:版本:goal 了吗?每一行都是一个「插件目标」在生命周期的某个阶段被执行——compiler:compile、surefire:test、jar:jar。这是 Maven 日志最该学会读的东西。
打出来的 jar(真实路径):
target/greet-0.1.0.jar
5. 经典一课:thin jar vs fat jar(运行它才发现的事)
直接 java -jar target/greet-0.1.0.jar 会翻车(真实输出):
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils
at com.example.Greet.main(Greet.java:9)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.lang3.StringUtils
原因:Maven 默认打的是 thin jar——只含你自己的类,不含第三方依赖。commons-lang3 虽然编译时在 classpath,但没被打进 jar,运行时找不到。
要跑起来,把依赖加进 classpath 即可(真实输出):
java -cp "target/greet-0.1.0.jar:$HOME/.m2/repository/org/apache/commons/commons-lang3/3.20.0/commons-lang3-3.20.0.jar" com.example.Greet Alice
# → Hello, Alice!
这段正好把三个概念串起来了:坐标(那条长长的 jar 路径就是坐标)→ 本地仓库(.m2 里的缓存)→ classpath 与 thin jar 的差别。
那 Spring Boot 怎么
java -jar xxx.jar就能跑? 因为 Boot 的spring-boot-maven-plugin在 package 阶段做了 repackage——把第三方依赖也塞进去,打成 fat jar(可执行 jar)。所以你在 Spring 入门 里看到的java -jar能直接跑,正是 Maven 插件机制干的事。想给自己的库打 fat jar,用maven-shade-plugin或参考 Boot 的 repackage。
6. 常用命令速查
| 命令 | 作用 |
|---|---|
mvn compile | 只编译到 target/classes |
mvn test | 编译 + 跑单测 |
mvn package | 跑到打 jar/war(不装进本地仓库) |
mvn install | package 后装进 ~/.m2,供同机其它项目引用 |
mvn clean | 删 target/(配合用:mvn clean package) |
mvn dependency:tree | 看依赖树(排查冲突神器) |
mvn -q / mvn -B | 安静 / 批处理(CI 常用) |
mvn -DskipTests package | 跳过测试打包 |
mvnw(Maven Wrapper):工程里带 mvnw/mvnw.cmd + .mvn/wrapper,第一次用会下载指定版本的 Maven——让没装 Maven 的人也能构建,且全队版本一致。Spring Boot 工程默认自带,用 ./mvnw package 即可。
7. 再往前一步
这篇够你读懂任何 Java 项目的 pom.xml 并在本地构建了。接着值得补的(另开子篇):
- 依赖作用域(scope):
compile / provided / runtime / test——为什么 Lombok 要用provided、JUnit 是test; - 传递依赖与冲突:A 依赖 B、B 依赖 C 时,版本谁说了算(就近原则)→
mvn dependency:tree; - parent 与 dependencyManagement:Spring Boot 的
spring-boot-starter-parent怎么帮你锁一整套版本(BOM); - profile 与私服:环境差异(dev/prod)、公司仓库镜像与
settings.xml。
参考
- 官网: maven.apache.org(指南、插件文档)
- 中央仓库检索:repo.maven.apache.org(或 search.maven.org)
- 关联:本站 Spring 入门(pom/mvnw 的实战上下文)
- 许可:Apache-2.0
- 本文基于 Maven 3.9.16 + JDK 11 实测(2026-09-08);Maven 4.0 仍为 RC,生产请用 3.9.x 稳定线。