Maven 核心机制学习笔记:POM 继承、Profiles、生命周期、依赖作用域与版本
Maven 的核心并不只是“下载依赖和执行 mvn package”,它实际上围绕 POM、构建生命周期、依赖管理、Profile 和版本体系组织整个 Java 工程的构建过程。本文把日常开发中最常用、也最容易混淆的几个点放在一起梳理。
POM 与项目继承
POM(Project Object Model)是 Maven 项目的核心描述文件。Maven 执行构建时,会先读取 pom.xml,得到项目坐标、依赖、插件、仓库以及构建配置。
一个项目可以通过 <parent> 继承父 POM:
1 | |
父 POM 中大多数配置都可以被子项目继承,常见的包括:
groupId、versiondescription、url、inceptionYearorganization、licensesdevelopers、contributors、mailingListsscm、issueManagement、ciManagementpropertiesdependenciesdependencyManagementrepositories、pluginRepositoriesbuild中的插件执行和插件配置reporting
需要特别注意,artifactId、name、prerequisites、profiles 本身不会像普通字段那样直接继承。对于父 POM 中已经激活的 Profile,子项目继承的是它产生的实际配置效果,而不是 Profile 定义本身。
实际排查继承结果时,可以直接查看 Maven 最终计算出来的 Effective POM:
1 | |
这比只盯着当前模块的 pom.xml 更可靠,因为 Maven 最终使用的是父 POM、Super POM、Profiles 和当前 POM 合并后的结果。
Maven Profiles 与 application.yml
Profile 用来根据不同构建环境修改 POM。例如开发、测试、生产环境使用不同参数时,可以在一个 pom.xml 中定义多套配置。
1 | |
通过 -P 激活:
1 | |
查看当前实际激活的 Profile:
1 | |
查看某个 Profile 生效后的完整 POM:
1 | |
Profile 中的值如何给 application.yml 使用
如果项目继承了 spring-boot-starter-parent,Spring Boot 已经为 application.properties 和 application.yml 提供了合适的 Maven Resource Filtering 配置。
由于 Spring 自己使用 ${...} 作为属性占位符,为避免和 Maven 占位符冲突,Spring Boot 默认建议 Maven 过滤时使用 @...@。
例如 POM:
1 | |
在 application.yml 中可以这样取:
1 | |
执行:
1 | |
在 process-resources 阶段,Maven 会处理 src/main/resources,最终生成到 target/classes/application.yml 中的内容已经被替换,例如:
1 | |
这里要区分两件事:
- Maven Profile 是构建期配置,
-Pdev决定这次构建怎么生成产物。 - Spring Profile 是应用运行期配置,也可以通过
--spring.profiles.active=dev、环境变量等方式决定。
如果希望“同一个 JAR 在 dev、test、prod 环境直接复用”,通常更适合把环境选择留到 Spring Boot 运行时,而不是使用 Maven Profile 把环境值永久写进构建产物。
Maven 默认构建生命周期
Maven 内置 default、clean、site 三套生命周期。其中日常使用最多的是 default 生命周期。
完整的 default 生命周期主要阶段按顺序为:
1 | |
开发过程中最常接触的是下面几个阶段:
| Phase | 作用 |
|---|---|
validate |
校验项目结构和必要信息是否完整 |
compile |
编译主代码 |
test |
执行单元测试 |
package |
将项目打包为 JAR、WAR 等产物 |
verify |
对构建和集成测试结果进行进一步校验 |
install |
将产物安装到本地 Maven 仓库 |
deploy |
将产物发布到远程 Maven 仓库 |
Maven 执行某一个阶段时,会自动执行它之前的所有阶段。
例如:
1 | |
并不是只执行 package,而是从 validate 一直执行到 package。
同理:
1 | |
会执行到 install,因此前面的编译、测试、打包等阶段也都会执行。
Maven 依赖作用域
依赖作用域(Dependency Scope)主要解决两个问题:
- 一个依赖应该出现在哪些 Classpath 中。
- 一个依赖是否继续向下传递为传递依赖。
Maven 官方定义了 6 种 Scope。
compile
默认作用域,不写 <scope> 时就是 compile。
1 | |
它会出现在编译、测试和运行 Classpath 中,并且可以作为传递依赖继续传播给依赖当前项目的其他项目。
provided
provided 表示:编译项目时需要,但是正式运行环境预计由 JDK、Servlet 容器或其他外部运行环境提供。
典型场景是传统 WAR 项目的 Servlet API:
1 | |
它会进入编译和测试 Classpath,但不会进入 Maven 的运行时 Classpath,也不会作为普通传递依赖传播。
这里有一个常见误解:provided 不是“编译和运行都由项目提供”,而是“编译时项目需要,运行时由外部环境提供”。
runtime
runtime 表示编译主代码时不需要,但程序真正运行时需要。
典型场景是 JDBC 驱动:
1 | |
业务代码通常面向 JDBC API 编程,真正的数据库驱动实现由运行时加载,因此驱动不需要进入主代码的编译 Classpath。
runtime 会进入运行和测试 Classpath。
test
只用于测试代码的编译和运行,不参与正式应用运行,也不会传递给依赖当前项目的其他项目。
1 | |
system
system 与 provided 有些相似,但 Maven 不会从仓库解析这个依赖,而是要求显式指定本机 JAR 路径。
1 | |
这种方式会让构建依赖本机目录结构,破坏构建可移植性,因此 Maven 官方并不推荐。更合适的方式通常是把内部 JAR 发布到公司的 Maven 私服。
import
import 比较特殊,它只允许:
type为pom- 位于
<dependencyManagement>中
它的主要用途就是导入 BOM:
1 | |
这并不是把一个普通依赖放到 Classpath,而是把目标 POM 中 dependencyManagement 管理的依赖版本导入当前项目。
Scope 对照
| Scope | 编译 | 测试 | 运行 | 传递性 | 常见用途 |
|---|---|---|---|---|---|
compile |
Y | Y | Y | Y | 普通业务依赖 |
provided |
Y | Y | N | N | Servlet API、容器提供的 API |
runtime |
N | Y | Y | Y | JDBC 驱动等运行时实现 |
test |
N | Y | N | N | JUnit、Mockito |
system |
Y | Y | 取决于本机显式路径 | N | 本地遗留 JAR,不推荐 |
import |
- | - | - | - | 在 dependencyManagement 中导入 BOM |
需要注意,Scope 本质上控制的是 Classpath 和依赖传递关系,不能简单等同于“最终 JAR 中有没有这个依赖”。普通 Maven JAR 默认并不会把所有依赖直接塞进 JAR;Spring Boot Fat JAR、WAR 等具体打包行为还取决于对应插件和 Packaging 类型。
工程版本号约定
Maven 使用 groupId:artifactId:version 唯一确定一个构件,其中 version 本身并不强制要求必须使用某一种版本模型。
实际项目中最常见的是类似语义化版本的形式:
1 | |
例如:
1 | |
通常可以这样理解:
major:不兼容的大版本变更。minor:向后兼容的功能增加。patch:缺陷修复和较小改动。M1、RC1等 qualifier:里程碑版本、候选发布版本等预发布标记。
旧版 Spring 项目中经常能看到:
1 | |
这种写法属于历史上的项目版本命名习惯,并不是 Maven 强制规定。当前 Maven 官方版本命名建议中,也更推荐正式发布版本直接使用:
1 | |
而不是额外增加 RELEASE qualifier。
SNAPSHOT 与 Release
开发阶段常见:
1 | |
SNAPSHOT 表示当前版本仍然处于开发过程中,它对应的是某个时间点的开发构建,同一个 1.0.0-SNAPSHOT 后续可以继续被新的构件覆盖或更新。
例如:
1 | |
最终发布后通常变成:
1 | |
随后项目进入下一个开发周期:
1 | |
因此可以把它理解为:
1 | |
Maven 对 -SNAPSHOT 有专门的仓库处理逻辑,Snapshot 仓库和 Release 仓库也可以分别配置更新策略。
总结
理解 Maven 时,可以把几个概念串成一条线:
1 | |
日常排查 Maven 问题时,下面几条命令非常实用:
1 | |
参考资料
- Apache Maven - POM Reference
- Apache Maven - Introduction to Build Profiles
- Apache Maven - Introduction to the Build Lifecycle
- Apache Maven - Introduction to the Dependency Mechanism
- Apache Maven - Naming conventions of Maven coordinates
- Apache Maven - Getting Started Guide
- Spring Boot - Using the Maven Plugin