目标:理解 Maven 解决什么问题,掌握 POM、坐标、仓库和基础命令,能够从零创建并构建一个标准 Java 项目。
目录
- [Maven 是什么](#Maven 是什么)
- 安装与配置
- [POM 文件解析](#POM 文件解析)
- 坐标系统与版本语义
- 仓库管理
- 基础命令全流程
- [实战 Demo:maven-hello-world](#实战 Demo:maven-hello-world)
- 常见问题与避坑
- 专家面试题
1. Maven 是什么
Maven 是 Java 生态中最常用的项目构建与依赖管理工具。它把一个 Java 项目的构建信息集中写在 pom.xml 中,由 Maven 根据固定生命周期完成编译、测试、打包、安装、发布等工作。
Maven 核心价值
| 能力 | 解决的问题 | 典型场景 |
|---|---|---|
| 项目对象模型 POM | 项目信息、依赖、插件、构建规则集中管理 | 多人协作时保持构建一致 |
| 约定优于配置 | 默认目录、默认生命周期、默认插件绑定 | 新项目无需手写大量脚本 |
| 依赖管理 | 自动下载依赖、处理传递依赖 | 引入 Spring Boot、JUnit、数据库驱动 |
| 生命周期 | 把构建流程拆成标准阶段 | compile、test、package、install |
| 插件扩展 | 所有构建能力都由插件实现 | 编译、测试、打包、代码检查、发布 |
Maven 与 Ant、Gradle 对比
| 维度 | Ant | Maven | Gradle |
|---|---|---|---|
| 核心定位 | 构建脚本工具 | 标准化构建与依赖管理 | 可编程构建平台 |
| 配置方式 | XML 任务脚本 | XML POM 模型 | Groovy/Kotlin DSL |
| 默认约定 | 少,需要手写 | 强,约定优于配置 | 强,可灵活扩展 |
| 依赖管理 | 原生较弱 | 成熟稳定 | 成熟且支持变体 |
| 学习重点 | 任务编排 | 生命周期、POM、插件 | Task、Plugin、DSL、缓存 |
Maven 的优势是稳定、标准、生态广。企业 Java 后端项目、Spring Boot 项目、开源组件发布仍大量使用 Maven。
2. 安装与配置
环境要求
bash
java -version
mvn -version
建议:
| 工具 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 17+ | Spring Boot 3.x 和现代企业项目推荐 Java 17 |
| Maven | 3.9+ | 依赖解析、安全和性能更好 |
| IDE | IntelliJ IDEA | 对 Maven 项目导入、依赖树、Profile 支持完整 |
Maven 目录结构
text
apache-maven-3.9.x/
├── bin/ # mvn、mvnDebug 命令
├── boot/ # Maven 自启动依赖
├── conf/settings.xml # 全局配置
└── lib/ # Maven 运行依赖
settings.xml 关键配置
settings.xml 不是项目配置,而是"用户或机器级配置"。常见位置:
text
全局配置:${MAVEN_HOME}/conf/settings.xml
用户配置:~/.m2/settings.xml
常用配置示例:
xml
<settings>
<localRepository>/Users/yourname/.m2/repository</localRepository>
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven Central Mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
<servers>
<server>
<id>company-releases</id>
<username>${env.MAVEN_REPO_USER}</username>
<password>${env.MAVEN_REPO_PASSWORD}</password>
</server>
</servers>
<profiles>
<profile>
<id>company</id>
<repositories>
<repository>
<id>company-public</id>
<url>https://repo.example.com/repository/maven-public/</url>
</repository>
</repositories>
</profile>
</profiles>
</settings>
重要原则:
| 配置 | 放在哪里 | 原因 |
|---|---|---|
| 依赖版本、插件版本 | 项目 pom.xml |
项目成员必须一致 |
| 私服账号密码 | settings.xml 或 CI Secret |
避免提交敏感信息 |
| 本地仓库路径 | settings.xml |
属于个人机器配置 |
| Profile 环境变量 | POM 或 settings | 项目环境放 POM,个人环境放 settings |
3. POM 文件解析
POM 是 Project Object Model 的缩写,描述一个项目的身份、依赖、构建规则和发布方式。
最小 POM:
xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>maven-hello-world</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
</project>
GAV 坐标
| 字段 | 含义 | 示例 |
|---|---|---|
groupId |
组织或公司域名反写 | com.example.maven.demo |
artifactId |
模块或产物名称 | maven-demo-core |
version |
版本号 | 1.0.0-SNAPSHOT |
一个依赖的完整身份由 groupId:artifactId:version 决定。
packaging 类型
| 类型 | 产物 | 常见用途 |
|---|---|---|
jar |
.jar |
Java 库、Spring Boot 应用 |
war |
.war |
传统 Servlet 容器部署 |
pom |
无代码产物 | 父工程、BOM、聚合工程 |
maven-plugin |
Maven 插件 JAR | 自定义插件 |
本项目中:
text
maven-demo packaging=pom
maven-demo-bom packaging=pom
maven-demo-api packaging=jar
maven-demo-core packaging=jar
maven-demo-plugin packaging=maven-plugin
maven-demo-web packaging=jar
properties
properties 用来集中定义版本和构建参数:
xml
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.5</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
最佳实践:
- 业务依赖版本集中定义,避免散落在每个模块。
- Java 编译版本写在父 POM。
- 不把账号密码写进
properties。
4. 坐标系统与版本语义
SNAPSHOT vs RELEASE
| 类型 | 示例 | 语义 | 使用场景 |
|---|---|---|---|
| SNAPSHOT | 1.0.0-SNAPSHOT |
开发快照,可能被覆盖 | 内部开发联调 |
| RELEASE | 1.0.0 |
稳定发布,不应被覆盖 | 正式发布、生产依赖 |
SNAPSHOT 依赖会被 Maven 按更新策略检查远程仓库,RELEASE 版本默认认为不可变。因此生产系统应依赖 RELEASE,不应依赖 SNAPSHOT。
语义化版本
推荐格式:
text
主版本.次版本.修订版本
MAJOR.MINOR.PATCH
| 变化 | 示例 | 含义 |
|---|---|---|
| PATCH | 1.0.0 -> 1.0.1 |
Bug 修复,兼容 |
| MINOR | 1.0.0 -> 1.1.0 |
新功能,兼容 |
| MAJOR | 1.0.0 -> 2.0.0 |
不兼容变更 |
5. 仓库管理
Maven 仓库是依赖和构建产物的存储位置。
仓库类型
| 类型 | 默认位置或地址 | 作用 |
|---|---|---|
| 本地仓库 | ~/.m2/repository |
缓存依赖,存放 mvn install 的产物 |
| 中央仓库 | https://repo.maven.apache.org/maven2 |
官方公共依赖仓库 |
| 远程仓库 | 公司 Nexus/Artifactory | 私有依赖、代理缓存、发布产物 |
| 镜像 | 阿里云、腾讯云等 | 加速中央仓库下载 |
依赖解析顺序
text
本地仓库
↓ 未命中
远程仓库 / 镜像
↓ 下载
写入本地仓库
常见误区:
- 删除本地仓库可以解决缓存损坏,但会导致依赖重新下载。
mirrorOf=*会镜像所有仓库,可能影响公司私服发布。repositories放在 POM 中会影响所有成员,应该谨慎添加。
6. 基础命令全流程
| 命令 | 做什么 | 是否会执行前置阶段 |
|---|---|---|
mvn validate |
校验项目结构和配置 | 否 |
mvn compile |
编译主代码 | 是 |
mvn test |
编译并运行测试 | 是 |
mvn package |
打包产物 | 是 |
mvn install |
安装到本地仓库 | 是 |
mvn deploy |
发布到远程仓库 | 是 |
mvn clean |
删除 target/ |
clean 生命周期 |
示例:
bash
cd maven-demo
mvn clean package
关键输出:
text
[INFO] Reactor Summary for maven-demo 1.0.0-SNAPSHOT:
[INFO] maven-demo-bom ................................ SUCCESS
[INFO] maven-demo-api ................................ SUCCESS
[INFO] maven-demo-core ............................... SUCCESS
[INFO] maven-demo-plugin ............................. SUCCESS
[INFO] maven-demo-web ................................ SUCCESS
7. 实战 Demo:maven-hello-world
如果从零创建单模块项目,可按下面结构:
text
maven-hello-world/
├── pom.xml
└── src/
├── main/java/com/example/App.java
└── test/java/com/example/AppTest.java
pom.xml:
xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>maven-hello-world</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
App.java:
java
package com.example;
public class App {
public static void main(String[] args) {
System.out.println("Hello Maven");
}
}
运行:
bash
mvn clean package
java -cp target/maven-hello-world-1.0.0-SNAPSHOT.jar com.example.App
预期输出:
text
Hello Maven
本仓库的综合 Demo 位于 maven-demo/,它是该单模块项目的企业化扩展版本。
8. 常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
Source option 5 is no longer supported |
编译插件默认源码版本太旧 | 配置 maven-compiler-plugin 的 release |
| 依赖下载很慢 | 访问中央仓库慢 | 配置镜像或公司私服代理 |
| IDE 能运行,命令行不能运行 | IDE 使用的 JDK/Maven 与终端不一致 | 对比 java -version、mvn -version |
| 本地依赖一直不更新 | SNAPSHOT 缓存或 metadata 问题 | 使用 mvn -U 或清理对应依赖目录 |
| 多人构建结果不同 | Maven/JDK/settings 不一致 | 使用 Maven Wrapper 和统一父 POM |
9. 专家面试题
Q1:Maven 的"约定优于配置"体现在哪里?
答:Maven 规定了标准目录结构、默认生命周期、默认插件绑定和默认产物位置。例如主代码默认在 src/main/java,测试代码默认在 src/test/java,mvn package 默认会编译、测试并打包。这样团队不需要为每个项目重复定义构建脚本。它的代价是灵活性低于 Gradle,但换来更强的一致性。
Q2:为什么 Maven 坐标要有 groupId、artifactId、version 三个字段?
答:artifactId 只能描述模块名,无法区分不同组织的同名模块;version 用于区分同一模块的不同发布版本。三者组合后才能在仓库中唯一定位一个产物。实际依赖解析还会叠加 packaging/type 和 classifier,但 GAV 是最核心的身份。
Q3:mvn install 和 mvn deploy 的区别是什么?
答:install 把当前项目产物安装到本机 ~/.m2/repository,供本机其他项目依赖;deploy 把产物发布到远程仓库,供团队或 CI 使用。开发本地联调用 install,正式共享版本用 deploy。
Q4:为什么生产不建议依赖 SNAPSHOT?
答:SNAPSHOT 是可变版本,同一个 1.0.0-SNAPSHOT 在不同时间可能对应不同内容,导致构建不可复现。生产发布应依赖不可变 RELEASE 版本,并配合制品仓库权限禁止覆盖。
10. 入门篇扩展核查:必须补齐的基础知识
10.1 Maven 不是"下载 jar 的工具"
很多初学者把 Maven 理解成"自动下载 jar 包"。这个理解只覆盖了 Maven 的依赖管理能力,没有覆盖它的构建模型。
Maven 实际包含四层能力:
| 层级 | 说明 | 示例 |
|---|---|---|
| 项目建模 | 描述项目身份、模块、依赖、插件、发布信息 | pom.xml |
| 依赖解析 | 从仓库解析直接依赖和传递依赖 | mvn dependency:tree |
| 构建执行 | 按生命周期执行插件 Goal | mvn clean package |
| 制品发布 | 把 JAR、源码包、Javadoc、POM 发布到仓库 | mvn deploy |
所以 Maven 的学习顺序不能只停留在 <dependency>。如果不知道生命周期和插件,就无法解释为什么 mvn package 会先编译、测试再打包;如果不知道仓库和发布,就无法解释公司内部 SDK 如何被其他项目依赖。
10.2 Super POM 与 Effective POM
每个 Maven 项目都会隐式继承 Super POM。Super POM 提供了一组基础默认值,例如中央仓库、默认构建目录、默认插件绑定等。
查看当前项目最终 POM:
bash
cd maven-demo
mvn help:effective-pom
建议重点观察:
text
<repositories>
<pluginRepositories>
<build>
<plugins>
<dependencyManagement>
<profiles>
排查经验:
- 你在当前 POM 找不到的插件配置,可能来自父 POM 或 Super POM。
- 你以为没激活的 Profile,可能被 settings 或默认激活规则打开。
- 依赖版本不符合预期时,先看 Effective POM,再看依赖树。
10.3 Maven 标准目录为什么重要
Maven 通过标准目录实现"约定优于配置"。
| 目录 | Maven 默认行为 | 常见错误 |
|---|---|---|
src/main/java |
编译为主 classpath | 把业务代码放到错误目录导致不编译 |
src/main/resources |
复制到 target/classes |
配置文件没进包 |
src/test/java |
编译为测试 classpath | 测试代码引用主代码失败 |
src/test/resources |
复制到 target/test-classes |
测试找不到测试配置 |
target |
构建输出目录 | 手工修改 target 文件后被 clean 删除 |
验证:
bash
cd maven-demo
mvn -pl maven-demo-core clean package
find maven-demo-core/target -maxdepth 3 -type f
你会看到:
text
target/classes/build-info.properties
target/maven-demo-core-1.0.0-SNAPSHOT.jar
target/surefire-reports/...
10.4 archetype 创建项目
Maven archetype 是项目模板机制,适合快速创建标准项目。
bash
mvn archetype:generate \
-DgroupId=com.example \
-DartifactId=maven-hello-world \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
创建后建议立即检查:
bash
cd maven-hello-world
mvn test
mvn package
注意:archetype 生成的模板可能比较旧,例如 JUnit 版本、Java 编译版本不符合现代项目要求。生成后要升级 maven-compiler-plugin、JUnit 和编码配置。
10.5 本地仓库路径如何映射
依赖坐标:
text
org.apache.commons:commons-lang3:3.14.0
本地仓库路径:
text
~/.m2/repository/org/apache/commons/commons-lang3/3.14.0/
文件通常包括:
text
commons-lang3-3.14.0.jar
commons-lang3-3.14.0.pom
_remote.repositories
*.sha1
如果依赖损坏,可以只删除对应版本目录,而不是删除整个 ~/.m2/repository。
10.6 Maven 离线模式
离线模式只使用本地仓库,不访问远程仓库:
bash
mvn -o package
适用场景:
- CI 缓存已预热。
- 内网环境暂时不能访问外部仓库。
- 验证项目是否具备可复现构建能力。
如果缺依赖,离线模式会失败。可以先执行:
bash
mvn dependency:go-offline
10.7 IDE 导入 Maven 项目的核查点
在 IntelliJ IDEA 中导入 Maven 项目后,应检查:
| 检查项 | 正确状态 |
|---|---|
| Project SDK | JDK 17 或更高 |
| Maven home | 与命令行 Maven 版本一致 |
| User settings file | 指向正确 settings.xml |
| Local repository | 与命令行一致 |
| Profiles | dev/test/prod 是否按需激活 |
| Importing JVM | 不要使用过旧 JDK |
命令行能构建但 IDE 报错,优先比较 IDEA Maven 设置和 mvn -version。
10.8 入门阶段实操任务
| 任务 | 命令 | 验收标准 |
|---|---|---|
| 查看 Maven 版本 | mvn -version |
Maven 3.9+ |
| 查看 Effective POM | mvn help:effective-pom |
能找到插件和依赖管理 |
| 构建 API 模块 | mvn -pl maven-demo-api test |
测试通过 |
| 查看本地仓库 | ls ~/.m2/repository/com/example/maven/demo |
能找到安装后的 Demo 产物 |
| 离线构建 | mvn -o package |
本地依赖齐全时成功 |
10.9 入门篇新增面试题
Q5:Super POM 的作用是什么?
答:Super POM 是所有 Maven 项目隐式继承的基础 POM,提供默认仓库、默认构建目录、默认插件配置等。它让一个最小 POM 也能执行标准生命周期。排查真实构建行为时,应通过 Effective POM 查看 Super POM、父 POM、当前 POM 和 Profile 合并后的结果。
Q6:为什么不建议把本地 jar 用 system scope 引入?
答:system scope 依赖不可传递,依赖路径通常写死在本机文件系统中,CI 和其他开发者机器上很容易失败。正确做法是把 jar 发布到 Nexus/Artifactory,或者至少使用 mvn install:install-file 安装到本地仓库并记录来源。
Q7:Maven 本地仓库可以直接删除吗?
答:可以,但不建议作为第一选择。删除整个仓库会导致所有依赖重新下载,耗时且可能受网络影响。更合理的方式是删除损坏依赖的具体版本目录,或者使用 mvn -U 强制更新 SNAPSHOT。