Maven 核心概念
理解 Maven 的五大核心概念是掌握 Maven 的关键。这五个概念不是孤立的,而是相互关联形成一个完整的构建体系。本文将详细解释每个概念及其关系,帮助你建立完整的 Maven 知识框架。
为什么需要理解核心概念
只会用命令不够
很多开发者只知道 mvn clean install,但遇到问题时不知所措:
场景:依赖版本冲突报错
不懂概念:只知道报错,不知道 dependency:tree 能诊断
理解概念:知道依赖传递机制,知道如何用 exclusions 解决
场景:多模块项目构建顺序问题
不懂概念:随意放 modules,构建失败
理解概念:知道反应堆机制,知道依赖方向决定构建顺序
场景:环境配置切换
不懂概念:手动修改配置文件
理解概念:知道 Profile 概念,知道 -Pdev 切换环境
结论:理解核心概念才能高效使用 Maven,遇到问题能快速定位解决。
五大核心概念关系图
┌──────────────────┐
│ POM │
│ 项目对象模型 │
│ (核心配置文件) │
└──────────┬───────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ 坐标 │ │ 依赖 │ │ 插件 │
│定位构件 │ │引用库 │ │执行任务 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└──────────────┼──────────────┘
│
┌──────────▼───────────┐
│ 生命周期 │
│ 构建阶段顺序 │
│ (compile→test→package)│
└───────────────────────┘
理解这个关系:POM 是配置中心,坐标定位依赖和插件,依赖声明项目需要的外部库,插件执行具体构建任务,生命周期定义执行顺序。
POM(Project Object Model)
什么是 POM
POM 是 Maven 项目的核心配置文件,以 XML 格式描述项目的所有信息:
- 项目基本信息(名称、描述、版本)
- 项目依赖(需要哪些第三方库)
- 构建配置(源码目录、输出目录)
- 插件配置(如何编译、打包)
- 环境配置(多环境 Profile)
POM 层次结构
超级 POM(内置)
↓ 继承
父 POM(可选)
↓ 继承
项目 POM(pom.xml)
↓ 合并
有效 POM(Effective POM)
关键理解:每个项目的 pom.xml 都继承了超级 POM 的默认配置。你写的 pom.xml 是对默认配置的覆盖和补充。
超级 POM 提供了什么
超级 POM 内置了 Maven 的所有默认配置:
| 默认配置 | 值 |
|---|---|
| 源码目录 | src/main/java |
| 测试目录 | src/test/java |
| 输出目录 | target/classes |
| 仓库地址 | https://repo.maven.apache.org/maven2 |
| 默认插件 | compiler、jar、surefire 等 |
这就是"约定优于配置":你不需要声明这些,Maven 已经默认配置好了。
查看 POM 配置
# 查看合并后的完整配置(包含继承的配置)
mvn help:effective-pom
POM 最小结构
<project>
<modelVersion>4.0.0</modelVersion> <!-- POM 版本,固定值 -->
<!-- 项目坐标:唯一标识项目 -->
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<!-- 其他配置都是可选的 -->
</project>
实际开发:一个能跑的 Maven 项目,只需要这4个元素。
坐标(Coordinates)
为什么需要坐标
问题场景:如何唯一标识全球数百万个 jar 包?
有叫 "spring" 的 jar 吗?可能有几十个
有叫 "core" 的 jar 吗?可能有几百个
版本呢?同一个项目有多个版本
如何唯一标识?
需要一个全球唯一的标识系统 → Maven 坐标
坐标三要素(GAV)
groupId:artifactId:version
| 元素 | 作用 | 示例 | 选择原则 |
|---|---|---|---|
| groupId | 组织标识 | org.springframework | 反向域名,保证全球唯一 |
| artifactId | 项目名称 | spring-core | 项目/模块名,组织内唯一 |
| version | 版本号 | 5.3.20 | 标识同一项目的不同版本 |
坐标的实际用途
用途1:唯一标识构件
坐标:org.springframework:spring-core:5.3.20
全球唯一:只有 Spring 社区的 spring-core 5.3.20 版本
是这个坐标,不会和其他项目混淆
用途2:依赖声明
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.20</version>
</dependency>
<!-- Maven 根据坐标找到并下载这个 jar -->
用途3:仓库路径映射
坐标 → 仓库路径
org.springframework → org/springframework/
spring-core → spring-core/
5.3.20 → 5.3.20/
完整路径:
org/springframework/spring-core/5.3.20/spring-core-5.3.20.jar
版本号的含义
主版本.次版本.增量版本-里程碑版本
示例:
1.0.0 → 正式版
2.0.0-alpha → 内部测试版
2.0.0-beta → 公开测试版
2.0.0-RC1 → 候选发布版
2.0.0 → 正式发布版
1.0.0-SNAPSHOT → 开发快照版(不稳定)
重要区别:
| 版本类型 | 特性 | 使用场景 |
|---|---|---|
| SNAPSHOT | 内容随时变化,每次检查更新 | 开发期间团队内部使用 |
| RELEASE | 内容固定,不会变化 | 生产环境使用 |
实际建议:生产项目不要使用 SNAPSHOT 版本依赖,因为无法保证构建结果一致。
依赖(Dependency)
什么是依赖
依赖是项目需要的外部库,如 Spring、JUnit、MySQL驱动等。Maven 自动管理依赖的下载、版本和传递。
依赖声明
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.20</version>
<scope>compile</scope> <!-- 依赖范围 -->
</dependency>
</dependencies>
依赖范围(scope)—— 重要概念
scope 决定依赖在不同阶段的可见性:
| scope | 编译时 | 测试时 | 运行时 | 打包时 | 实际场景 |
|---|---|---|---|---|---|
| compile(默认) | ✓ | ✓ | ✓ | ✓ | Spring 等核心库 |
| test | ✗ | ✓ | ✗ | ✗ | JUnit 等测试库 |
| provided | ✓ | ✓ | ✗ | ✗ | Servlet API(Tomcat提供) |
| runtime | ✗ | ✓ | ✓ | ✓ | MySQL驱动 |
实际应用场景:
<!-- 场景1:核心库,全程需要 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.20</version>
<scope>compile</scope> <!-- 默认值,可省略 -->
</dependency>
<!-- 场景2:测试库,只测试时用 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope> <!-- 不打包进 jar -->
</dependency>
<!-- 场景3:容器提供的库 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope> <!-- Tomcat 提供,不打包 -->
</dependency>
<!-- 场景4:运行时才需要的库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<scope>runtime</scope> <!-- JDBC代码用接口,运行时加载驱动 -->
</dependency>
传递性依赖
概念:A 依赖 B,B 依赖 C,则 A 自动获得对 C 的依赖。
你的项目
↓ 声明依赖
spring-context
↓ 传递依赖
spring-core
↓ 传递依赖
spring-jcl(日志桥接)
你的项目自动获得:spring-context、spring-core、spring-jcl
实际好处:
- 你只需要声明直接依赖
- 间接依赖自动引入
- 避免遗漏依赖导致编译错误
查看传递依赖:
mvn dependency:tree
输出:
[INFO] com.example:my-app:jar:1.0.0
[INFO] +- org.springframework:spring-context:jar:5.3.20
[INFO] | +- org.springframework:spring-core:jar:5.3.20
[INFO] | +- org.springframework:spring-jcl:jar:5.3.20
插件(Plugin)
什么是插件
插件是执行具体构建任务的工具。Maven 本身不编译代码、不打包,这些工作由插件完成:
| 任务 | 执行插件 |
|---|---|
| 编译 Java 代码 | maven-compiler-plugin |
| 执行单元测试 | maven-surefire-plugin |
| 打包成 JAR | maven-jar-plugin |
| 打包成 WAR | maven-war-plugin |
| 清理 target 目录 | maven-clean-plugin |
插件与生命周期的关系
插件目标绑定到生命周期阶段:
生命周期阶段 绑定的插件目标
compile → compiler:compile(编译源码)
test → surefire:test(执行测试)
package → jar:jar(打包 JAR)
install → install:install(安装到本地仓库)
deploy → deploy:deploy(发布到远程仓库)
执行过程:
你执行:mvn package
Maven 执行:
1. 执行 compile 阶段 → compiler:compile 编译源码
2. 执行 test 阶段 → surefire:test 执行测试
3. 执行 package 阶段 → jar:jar 打包
自动按顺序执行,无需手动调用每个插件
插件配置示例
<build>
<plugins>
<!-- 配置编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source> <!-- Java 源码版本 -->
<target>17</target> <!-- 编译输出版本 -->
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
常用内置插件
| 插件 | 功能 | 默认绑定阶段 |
|---|---|---|
| maven-compiler-plugin | 编译源码 | compile |
| maven-surefire-plugin | 执行测试 | test |
| maven-jar-plugin | 打包 JAR | package |
| maven-war-plugin | 打包 WAR | package |
| maven-clean-plugin | 清理 | clean |
| maven-install-plugin | 安装到本地 | install |
| maven-deploy-plugin | 发布到远程 | deploy |
| maven-resources-plugin | 处理资源 | process-resources |
生命周期(Lifecycle)
什么是生命周期
生命周期定义了构建阶段的顺序。Maven 有三个独立的生命周期:
Clean 生命周期:清理相关
pre-clean → clean → post-clean
Default 生命周期:核心构建
validate → compile → test → package → verify → install → deploy
Site 生命周期:文档生成
pre-site → site → post-site → site-deploy
生命周期执行规则—— 核心
执行某个阶段时,会自动执行该阶段之前的所有阶段:
mvn compile
# 执行:validate → compile
mvn test
# 执行:validate → compile → test-compile → test
mvn package
# 执行:validate → compile → test → package
mvn install
# 执行:validate → compile → test → package → verify → install
实际意义:
- 你不需要手动执行每个阶段
- 只需要执行目标阶段,前置阶段自动执行
- 确保构建流程完整
常用构建命令
| 命令 | 执行的阶段 | 产出 |
|---|---|---|
mvn compile | validate → compile | target/classes/*.class |
mvn test | compile → test | target/surefire-reports/测试报告 |
mvn package | compile → test → package | target/*.jar 或 *.war |
mvn install | package → install | 本地仓库中的构件 |
mvn deploy | install → deploy | 远程仓库中的构件 |
mvn clean | clean | 删除 target 目录 |
组合命令
# 最常用的组合:清理后重新构建
mvn clean package # 清理 + 编译 + 测试 + 打包
mvn clean install # 清理 + 构建 + 安装到本地
mvn clean deploy # 清理 + 构建 + 发布到远程
# 跳过测试(发布时常用)
mvn clean package -DskipTests
三大生命周期独立性
三个生命周期互不影响:
mvn clean # 只执行 clean 生命周期
mvn compile # 只执行 default 生命周期
mvn site # 只执行 site 生命周期
# 组合执行
mvn clean compile # 先 clean,再 default
mvn clean package # 先 clean,再 default 到 package
核心概念实际应用场景
场景1:新建 Maven 项目
步骤:
1. 确定项目坐标
groupId: com.mycompany
artifactId: my-project
version: 1.0.0
2. 创建 pom.xml(POM概念)
填入坐标,添加依赖
3. 添加依赖(依赖概念)
Spring、JUnit 等
4. 执行构建(生命周期概念)
mvn clean package
场景2:依赖版本冲突
问题:A 和 B 都依赖 C,但版本不同
理解概念后解决:
1. 知道依赖传递机制 → A 和 B 都会把 C 传给你
2. 知道版本选择规则 → 最短路径优先
3. 知道解决方法 → 直接声明版本或 exclusions
mvn dependency:tree # 查看(依赖概念)
添加 exclusions # 解决(依赖概念)
场景3:环境配置切换
需求:dev/test/prod 不同配置
理解概念后解决:
1. 知道 POM 可配置 properties
2. 知道 Profile 可激活不同配置
3. 使用 -Pdev 或 -Pprod 切换(Profile 概念)
场景4:自定义构建任务
需求:打包前执行代码生成
理解概念后解决:
1. 知道插件可执行任务
2. 知道插件可绑定到生命周期阶段
3. 配置插件绑定到 generate-sources 阶段(插件概念)
核心概念速查表
| 概念 | 核心要点 | 关键文件/命令 |
|---|---|---|
| POM | 项目配置中心,继承超级 POM | pom.xml、effective-pom |
| 坐标 | GAV 唯一标识构件 | groupId:artifactId:version |
| 依赖 | 声明外部库,scope控制范围 | dependency、dependency:tree |
| 插件 | 执行构建任务,绑定到阶段 | plugins、compiler:compile |
| 生命周期 | 定义阶段顺序,触发前置阶段 | clean、compile、test、package |
要点总结
- 五大概念相互关联:POM 是配置中心,坐标标识构件,依赖声明需求,插件执行任务,生命周期定义顺序
- POM 继承超级 POM:默认配置来自超级 POM,你只需覆盖需要的内容
- 坐标保证全球唯一:GAV 组合唯一标识一个构件
- scope 决定依赖可见性:compile 全程可见,test 仅测试,provided 不打包,runtime 运行时加载
- 依赖自动传递:间接依赖自动引入,用 dependency:tree 查看
- 插件执行具体任务:编译、测试、打包都是插件完成的
- 生命周期自动触发前置阶段:mvn package 自动执行 compile、test
📝 发现内容有误?点击此处直接编辑