日期:2026年4月17日标签:Developer Handbook

Maven #

Maven 是 Java 生态的项目管理与构建工具。
本文的目标不是让 C++ / JavaScript 项目改用 Maven,而是借助你已掌握的 CMake、npm 心智模型,快速且准确地理解 Maven。


1. 先建立一个直觉:Maven 在解决什么问题? #

一个 Java 项目通常要处理三件事:

  1. 依赖下载与版本管理
  2. 编译、测试、打包、发布流程
  3. 团队统一目录结构和构建约定

Maven 的核心价值是:把这三件事标准化,让团队少写样板配置、少踩环境差异坑。

如果用你熟悉的工具做类比:

  • npm:都能声明依赖并从远端拉包。
  • CMake:都在描述“项目如何被构建”。
  • 但 Maven 更强调“约定优于配置”:目录、生命周期、默认插件绑定都已约定好。

2. Maven 项目结构(约定优于配置) #

典型 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 思路很像:源码和产物分离。


3. POM 是 Maven 的核心配置文件 #

pom.xml(Project Object Model)是 Maven 的基本工作单元。Maven 执行命令时会读取当前目录下的 POM。

一个最小可用 POM 至少包含:

  • modelVersion
  • groupId
  • artifactId
  • version

示例:

<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, 可以把它想成“库的身份证号”。


4. SNAPSHOT 是什么? #

先看两个版本号:

  • 1.0.0:发布版(release),应当是稳定且不可变
  • 1.0.0-SNAPSHOT:开发版快照,“还在演进中的版本”

SNAPSHOT 的准确含义是:开发分支当前状态的可迭代版本标记
它不承诺稳定不变;随着开发推进,远端仓库里同名 SNAPSHOT 可能被更新。

一个常见发布节奏:

  1. 开发期:1.0.0-SNAPSHOT
  2. 发布时:1.0.0
  3. 下一轮开发:1.1.0-SNAPSHOT

5. 仓库体系:Maven 从哪里拿依赖? #

先给定义:仓库(Repository)是存放构建产物(artifact)的地方

Maven 只有两类仓库:

  1. local repository(本地仓库)

    • 在你机器上,默认是 ~/.m2/repository
    • 作用:缓存已下载依赖,也保存你 mvn install 的本地产物
  2. remote repository(远程仓库)

    • 任何非本地仓库(HTTP/HTTPS/file 等协议)
    • 默认会使用 Maven Central:https://repo.maven.apache.org/maven2/

依赖解析的典型过程:

  1. 先查本地仓库
  2. 本地没有则去远程仓库下载
  3. 下载后回填到本地仓库,下次直接复用

有时候远程仓库下载太慢了,可以添加镜像仓库代替。 镜像(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>

6. 依赖管理:Maven 为什么省心 #

你只声明直接依赖,Maven 会自动解析传递依赖(transitive dependencies)。

例如:A 依赖 B,B 依赖 C。你引入 A 时,B 和 C 都会进入依赖图。

6.1 依赖作用域(scope) #

Maven 有 6 种 scope,日常最常用 4 种:

  • compile(默认):编译/测试/运行都可见,且可传递
  • test:仅测试编译与测试运行可见
  • runtime:运行和测试可见,编译主代码不可见
  • provided:编译和测试可见,运行期期望由 JDK 或容器提供

另外两种进阶 scope:

  • system:从本地固定路径找 jar(官方不推荐常规使用)
  • import:只用于 dependencyManagement 中导入 BOM

6.2 dependencyManagement 是什么(容易误解) #

dependencyManagement 的作用是集中管理版本与约束,不是自动引入依赖本体。

也就是说:

  • 在父 POM 里用 dependencyManagement 锁版本
  • 子模块里仍要在 <dependencies> 显式声明“我要用它”

7. 命令、生命周期、插件 goal:一条线讲透 #

7.1 三层概念 #

  • Lifecycle:生命周期(如 defaultcleansite
  • Phase:生命周期里的阶段(如 compiletestpackage
  • Goal:插件里的具体任务(如 compiler:compile

Phase 自己不干活,真正执行的是绑定到该 phase 的插件 goal。

7.2 一条真实执行链 #

当你执行:

mvn clean package

Maven 会做:

  1. clean 生命周期到 clean phase(执行 clean:clean
  2. default 生命周期到 package phase
  3. default 中会按顺序经过前置 phase(如 validate -> compile -> test -> package
  4. 每个 phase 触发其默认绑定 goal,例如(以 jar 打包类型为例):
    • compile 阶段常见绑定 compiler:compile
    • test 阶段常见绑定 surefire:test
    • package 阶段常见绑定 jar:jar

所以,“执行 mvn package”本质上是“执行到 package 这一站为止的一整条流水线,沿途由插件 goal 完成具体工作”。


8. 插件配置:什么时候需要你手动写? #

很多默认场景你不写插件也能跑,因为 packaging 已经给 phase 绑定了默认 goal。

你需要手动配置插件,通常是为了:

  • 改默认行为(如编译参数)
  • 在某个 phase 增加自定义动作

示例(在 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>

9. 多模块:继承(parent)和聚合(modules) #

9.1 继承(Inheritance) #

子模块在 <parent> 中声明父 POM,复用公共配置。

典型复用内容:

  • 统一版本
  • properties
  • pluginManagement
  • dependencyManagement

9.2 聚合(Aggregation) #

聚合 POM 在 <modules> 声明子模块路径;在聚合根目录执行一次命令可联动构建所有模块。

做聚合时,聚合 POM 的 packaging 通常为 pom

9.2.1 模块构建产物与 <packaging> #

<packaging> 用来声明这个模块最终要产出什么类型的构件(artifact)。

最常见几种:

  • jar:默认值;普通 Java 库或应用模块,产出 *.jar
  • war:Web 应用模块,产出 *.war
  • ear:企业应用归档,产出 *.ear
  • pom:通常用于父/聚合模块,通常不产出可运行二进制包
  • maven-plugin:Maven 插件模块,产出插件 JAR

注意两点:

  1. 如果不写 <packaging>,默认是 jar
  2. packaging 不只是“文件后缀”,它还会影响默认生命周期里哪些插件 goal 会被绑定和执行。

例如(默认绑定的直觉版理解):

  • jar 模块在 package 阶段常见执行 jar:jar
  • war 模块在 package 阶段常见执行 war:war
  • pom 模块更多承担组织与管理职责,构建动作与二进制打包模块不同

9.3 二者关系 #

  • parent 解决“配置复用”
  • modules 解决“批量构建”
  • 一个项目可以只用其一,也可以两者同时使用

10. 给 CMake / npm 开发者的快速映射 #

Maven类比到你熟悉的概念重点区别
pom.xmlCMakeLists.txt + package.jsonMaven 把依赖与构建都集中在 POM
GAV 坐标npm 包名+版本Maven 强调全局唯一坐标
生命周期npm scripts 流程Maven phase 顺序是框架约定
plugin goalnpm 脚本里调用的具体工具goal 可绑定到 phase 自动执行
本地仓库 .m2npm 缓存 + 本地制品池Maven 构件按统一仓库布局缓存

11. 新手高频坑(按优先级) #

  1. dependencyManagement 误当成“自动引依赖”
  2. 混淆 parent(继承)和 modules(聚合)
  3. 误用 provided 导致运行期缺类
  4. 只看直接依赖,不看 mvn dependency:tree
  5. 滥用 SNAPSHOT 到生产链路
  6. 不锁插件版本导致构建结果漂移

12. 最小命令清单(先会这几个) #

mvn -v
mvn compile
mvn test
mvn package
mvn install
mvn dependency:tree
mvn clean
  • install:发布到本机本地仓库
  • deploy:发布到远程仓库(团队共享)

13. 一句话收尾 #

Maven 不是“多一个命令”,而是 Java 团队协作里一套统一协议:

  • 用 POM 定义项目
  • 用仓库和坐标管理依赖
  • 用生命周期驱动 phase
  • 用插件 goal 执行真实动作

你已经懂 CMake 和 npm,所以学习 Maven 的关键不是背命令,而是把已有构建思维迁移到 Maven 的标准化模型上。


参考资料(仅 Maven 官方) #

目录