Maven 是 Java 生态的项目管理与构建工具。
本文的目标不是让 C++ / JavaScript 项目改用 Maven,而是借助你已掌握的 CMake、npm 心智模型,快速且准确地理解 Maven。
一个 Java 项目通常要处理三件事:
Maven 的核心价值是:把这三件事标准化,让团队少写样板配置、少踩环境差异坑。
如果用你熟悉的工具做类比:
npm:都能声明依赖并从远端拉包。CMake:都在描述“项目如何被构建”。典型 Maven 目录:
my-app/
├─ pom.xml
├─ src/
│ ├─ main/
│ │ ├─ java/
│ │ └─ resources/
│ └─ test/
│ ├─ java/
│ └─ resources/
└─ target/
pom.xml:项目描述与构建配置中心src/main/java:主源码src/test/java:测试源码target/:构建输出目录这和 CMake 的 out-of-source build 思路很像:源码和产物分离。
pom.xml(Project Object Model)是 Maven 的基本工作单元。Maven 执行命令时会读取当前目录下的 POM。
一个最小可用 POM 至少包含:
modelVersiongroupIdartifactIdversion示例:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
</project>
构件坐标(GAV) groupId:artifactId:version 用来唯一标识一个 Maven 构件, 例如:org.slf4j:slf4j-api:2.0.16, 可以把它想成“库的身份证号”。
先看两个版本号:
1.0.0:发布版(release),应当是稳定且不可变1.0.0-SNAPSHOT:开发版快照,“还在演进中的版本”SNAPSHOT 的准确含义是:开发分支当前状态的可迭代版本标记。
它不承诺稳定不变;随着开发推进,远端仓库里同名 SNAPSHOT 可能被更新。
一个常见发布节奏:
1.0.0-SNAPSHOT1.0.01.1.0-SNAPSHOT先给定义:仓库(Repository)是存放构建产物(artifact)的地方。
Maven 只有两类仓库:
local repository(本地仓库)
~/.m2/repositorymvn install 的本地产物remote repository(远程仓库)
https://repo.maven.apache.org/maven2/依赖解析的典型过程:
有时候远程仓库下载太慢了,可以添加镜像仓库代替。 镜像(mirror)本质上是“远程仓库的替代入口”,通常用于加速或内网治理。
slow ┌───────────────────┐
┌─────────────▶│Maven Central Repo.│
│ └───────────────────┘
│ │
│ │sync
│ ▼
┌───────┐ fast ┌───────────────────┐
│ User │─────────▶│Maven Mirror Repo. │
└───────┘ └───────────────────┘
再 settings.xml 配置镜像仓库:
<settings>
<mirrors>
<mirror>
<id>aliyun</id>
<name>aliyun</name>
<mirrorOf>central</mirrorOf>
<!-- 国内推荐阿里云的Maven镜像 -->
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
</settings>
你只声明直接依赖,Maven 会自动解析传递依赖(transitive dependencies)。
例如:A 依赖 B,B 依赖 C。你引入 A 时,B 和 C 都会进入依赖图。
Maven 有 6 种 scope,日常最常用 4 种:
compile(默认):编译/测试/运行都可见,且可传递test:仅测试编译与测试运行可见runtime:运行和测试可见,编译主代码不可见provided:编译和测试可见,运行期期望由 JDK 或容器提供另外两种进阶 scope:
system:从本地固定路径找 jar(官方不推荐常规使用)import:只用于 dependencyManagement 中导入 BOMdependencyManagement 是什么(容易误解) #dependencyManagement 的作用是集中管理版本与约束,不是自动引入依赖本体。
也就是说:
dependencyManagement 锁版本<dependencies> 显式声明“我要用它”Lifecycle:生命周期(如 default、clean、site)Phase:生命周期里的阶段(如 compile、test、package)Goal:插件里的具体任务(如 compiler:compile)Phase 自己不干活,真正执行的是绑定到该 phase 的插件 goal。
当你执行:
mvn clean package
Maven 会做:
clean 生命周期到 clean phase(执行 clean:clean)default 生命周期到 package phasedefault 中会按顺序经过前置 phase(如 validate -> compile -> test -> package)jar 打包类型为例):compile 阶段常见绑定 compiler:compiletest 阶段常见绑定 surefire:testpackage 阶段常见绑定 jar:jar所以,“执行 mvn package”本质上是“执行到 package 这一站为止的一整条流水线,沿途由插件 goal 完成具体工作”。
很多默认场景你不写插件也能跑,因为 packaging 已经给 phase 绑定了默认 goal。
你需要手动配置插件,通常是为了:
示例(在 package 阶段执行 shade):
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
子模块在 <parent> 中声明父 POM,复用公共配置。
典型复用内容:
聚合 POM 在 <modules> 声明子模块路径;在聚合根目录执行一次命令可联动构建所有模块。
做聚合时,聚合 POM 的 packaging 通常为 pom。
<packaging> #<packaging> 用来声明这个模块最终要产出什么类型的构件(artifact)。
最常见几种:
jar:默认值;普通 Java 库或应用模块,产出 *.jarwar:Web 应用模块,产出 *.warear:企业应用归档,产出 *.earpom:通常用于父/聚合模块,通常不产出可运行二进制包maven-plugin:Maven 插件模块,产出插件 JAR注意两点:
<packaging>,默认是 jar。packaging 不只是“文件后缀”,它还会影响默认生命周期里哪些插件 goal 会被绑定和执行。例如(默认绑定的直觉版理解):
jar 模块在 package 阶段常见执行 jar:jarwar 模块在 package 阶段常见执行 war:warpom 模块更多承担组织与管理职责,构建动作与二进制打包模块不同| Maven | 类比到你熟悉的概念 | 重点区别 |
|---|---|---|
pom.xml | CMakeLists.txt + package.json | Maven 把依赖与构建都集中在 POM |
| GAV 坐标 | npm 包名+版本 | Maven 强调全局唯一坐标 |
| 生命周期 | npm scripts 流程 | Maven phase 顺序是框架约定 |
| plugin goal | npm 脚本里调用的具体工具 | goal 可绑定到 phase 自动执行 |
本地仓库 .m2 | npm 缓存 + 本地制品池 | Maven 构件按统一仓库布局缓存 |
dependencyManagement 误当成“自动引依赖”parent(继承)和 modules(聚合)provided 导致运行期缺类mvn dependency:treemvn -v
mvn compile
mvn test
mvn package
mvn install
mvn dependency:tree
mvn clean
install:发布到本机本地仓库deploy:发布到远程仓库(团队共享)Maven 不是“多一个命令”,而是 Java 团队协作里一套统一协议:
你已经懂 CMake 和 npm,所以学习 Maven 的关键不是背命令,而是把已有构建思维迁移到 Maven 的标准化模型上。