05-Maven-第5章 进阶:多模块项目管理
05-Maven-第5章 进阶:多模块项目管理
ProriseMaven-第5章 进阶:多模块项目管理
摘要: 在真实的企业级项目中,一个系统通常会被拆分成多个相互关联的模块(例如 user-api, user-service 等)。本章我们将学习 Maven 为应对这种复杂性而提供的两大“法宝”:继承 (Inheritance) 与 聚合 (Aggregation)。掌握它们,是从开发单个项目迈向架构大型系统的关键一步。
5.1. 继承:抽取公共配置 (DRY)
[继承(Inheritance)是 Maven 中实现配置复用和版本统一的核心机制。它能帮助我们极大地简化多模块项目的配置,降低维护成本。]
5.1.1. 为何需要继承?
痛点分析: 想象一个典型的三层架构项目,我们将其拆分成了三个模块:
my-project-api: 定义接口和数据传输对象(DTO)。my-project-service: 实现核心业务逻辑。my-project-web: 提供 Web 接口。
在这种结构下,my-project-service 依赖 my-project-api,my-project-web 依赖 my-project-service。此时,我们会发现一个严重的问题:这三个模块都需要依赖 Spring Framework,并且它们的 Spring 版本必须完全一致。如果我们在每个模块的 pom.xml 文件中都单独配置 Spring 的依赖,当需要升级 Spring 版本时,就必须手动修改三个文件。项目越大,模块越多,这种维护工作就越容易出错和遗漏。这严重违反了软件开发的 DRY (Don't Repeat Yourself) 原则。
5.1.2. 父工程的角色与 <dependencyManagement>
解决方案: 为了解决上述问题,我们引入了“父工程”的概念。父工程是一个特殊的 Maven 项目,它的唯一职责就是作为所有子模块的“配置大管家”。
父工程的特征:
父工程的 pom.xml 中,<packaging> 标签的值必须是 pom。这告诉 Maven,这个项目本身不包含任何业务代码,也不需要被打成 jar 或 war 包,它仅仅用于管理配置。
1 | <groupId>com.mycompany</groupId> |
核心机制: 父工程通过 <dependencyManagement> 和 <pluginManagement> 两个核心标签来管理所有子模块的依赖和插件。
被定义在这两个标签内部的配置,作用仅仅是**“声明”和“锁定”版本**,并不会真正地将依赖引入到父工程中。它就像一个“版本字典”,供所有子模块查阅和遵守。
代码演示: 一个典型的父 pom.xml 配置如下:
1 | <properties> |
5.1.3. 子模块的实现
关联父工程: 子模块通过在自己的 pom.xml 中使用 <parent> 标签,来声明自己的“家谱”,指定谁是它的父工程。
1 | <parent> |
简化配置: 一旦子模块声明了父工程,当它需要引入被父工程“管理”的依赖时,就可以省略 <version> 标签。Maven 会自动向上查找父工程中 <dependencyManagement> 的声明,并使用那里定义的版本。
代码演示: 子模块的 pom.xml 变得异常简洁。
1 | <dependencies> |
通过“父工程管理版本,子模块引入依赖”的模式,我们实现了配置的集中化,极大地提高了项目的可维护性。当需要升级 Spring 版本时,只需修改父工程中一处 spring.version 的值即可。
5.2. 聚合:一键构建所有模块
[继承解决了配置复用的问题,但又带来了一个新的操作问题:我们如何才能一次性地构建所有相互关联的模块呢?聚合(Aggregation)正是为此而生。]
5.2.1. 为何需要聚合?
痛点分析: 假设我们的项目有 api, service, web 三个模块。在没有聚合的情况下,如果我们想完整地构建整个项目,就需要手动进入每一个子模块的目录,然后依次执行 mvn package。这个过程不仅繁琐,而且我们还必须非常清楚模块间的依赖关系,以保证正确的构建顺序(例如,必须先构建 api 模块,然后才能构建依赖它的 service 模块)。
5.2.2. 聚合的实现与 <modules>
解决方案: 聚合允许我们从一个“顶层”项目出发,一键构建所有它包含的子模块。
核心标签: 实现聚合非常简单,只需在父工程的 pom.xml 中使用 <modules> 标签,并在其中通过一个个 <module> 标签,注册所有它管理的子模块的目录名即可。
1 | <modules> |
通常,一个项目的父工程既承担着继承的职责(通过 <dependencyManagement>),也承担着聚合的职责(通过 <modules>),它们被定义在同一个父 pom.xml 文件中。
5.2.3. Maven反应堆
揭示“魔法”: 当我们在聚合工程(父工程)的根目录执行 Maven 命令(如 mvn package)时,Maven 的一个核心机制——反应堆 (Reactor)——就开始工作了。
核心概念: 反应堆的主要工作流程如下:
- 读取聚合配置: 首先,它会读取根
pom.xml中的<modules>列表,了解这个项目包含了哪些模块。 - 分析依赖关系: 接着,它会分析所有模块的
pom.xml,计算出模块之间完整的依赖关系图。 - 计算构建顺序: 基于依赖关系,反应堆会计算出一个正确的、唯一的构建顺序。例如,它知道
my-project-service依赖my-project-api,所以它一定会把my-project-api放在构建队列的前面。 - 顺序执行构建: 最后,反应堆会严格按照计算出的顺序,自动、依次地进入每个模块的目录,执行我们指定的 Maven 命令。
构建日志示例: 在执行聚合构建时,我们会在控制台的开头看到类似这样的 Reactor 构建计划:
1 | [INFO] Reactor Build Order: |
聚合与反应堆机制,让我们能够从宏观上掌控整个项目,实现“一键构建”,极大地简化了多模块项目的日常开发和持续集成流程。
5.3. 高频面试题
Q: 继承(Inheritance)和聚合(Aggregation)有什么区别和联系?
A: 这是考察多模块项目管理理解度的经典面试题。
1. 目标不同
- 继承的核心目标是复用配置,实现对依赖版本、插件版本等的统一管理,遵循 DRY 原则。它的着眼点是“是什么,怎么配”。
- 聚合的核心目标是便捷构建,让我们能够从一个地方一键构建多个相互关联的模块。它的着眼点是“怎么做,按什么顺序做”。
2. 实现方式不同
- 继承是通过在子模块的
pom.xml中使用<parent>标签指向父工程来实现的。这是一种“自下而上”的声明关系。 - 聚合是通过在父工程的
pom.xml中使用<modules>标签来列出所有子模块来实现的。这是一种“自上而下”的管理关系。
3. 关系
在绝大多数实际项目中,两者是相辅相成、同时使用的。通常,承担聚合职责的父工程,本身也是所有子模块的父工程。即,一个 packaging 为 pom 的项目,既在 <dependencyManagement> 中管理依赖,又在 <modules> 中聚合模块。可以简单理解为:
- 继承让子模块“知道”了它们的父亲是谁,从而可以“沿用”家规(配置)。
- 聚合让父工程“知道”了它有哪些孩子,从而可以在“召集”时(构建时)将所有孩子都叫上。





