← 返回题目列表

Maven 多模块项目是怎么组织的?聚合和继承有什么区别?

高频 中等 第 9 / 23 题 更新于 2026/08/03
Maven多模块聚合继承

简化版

Maven 多模块项目把一个大项目拆成多个子模块(如 commonserviceweb),每个模块独立又能协作,便于分层、复用、并行开发。它有两个容易混的概念:聚合(Aggregation)继承(Inheritance)聚合是「一个父模块统一管理并一起构建多个子模块」——父 pom 用 <modules> 列出子模块,mvn install 父项目时自动按依赖顺序构建所有子模块;继承是「子模块继承父 pom 的配置」——子模块用 <parent> 指定父 pom,从而复用父 pom 里的依赖版本管理(dependencyManagement)、插件配置、属性等,避免每个子模块重复写。一句话:聚合管「一起构建」(modules),继承管「配置复用」(parent),两者常一起用

详细版

多模块项目的典型结构

my-project/                    ← 父项目(packaging=pom)
├── pom.xml                    ← 父 pom(聚合 modules + 继承的公共配置)
├── my-common/                 ← 子模块:公共工具、实体
│   └── pom.xml
├── my-service/                ← 子模块:业务逻辑(依赖 common)
│   └── pom.xml
└── my-web/                    ← 子模块:Web 层(依赖 service)
    └── pom.xml

聚合(Aggregation)——父 pom 用 <modules> 列出并统一构建子模块:

<!-- 父 pom.xml -->
<packaging>pom</packaging>   <!-- 聚合/父项目必须是 pom 类型 -->
<modules>
    <module>my-common</module>
    <module>my-service</module>
    <module>my-web</module>
</modules>
<!-- 在父项目执行 mvn install,会按依赖顺序自动构建所有子模块 -->

继承(Inheritance)——子模块用 <parent> 继承父 pom 的配置:

<!-- 子模块 my-service/pom.xml -->
<parent>
    <groupId>com.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0</version>
</parent>
<artifactId>my-service</artifactId>
<!-- 从父 pom 继承:dependencyManagement(版本管理)、插件配置、属性、依赖等 -->
<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>my-common</artifactId>
        <!-- version 不用写!父 pom 的 dependencyManagement 统一管了 -->
    </dependency>
</dependencies>

聚合 vs 继承

维度聚合(Aggregation)继承(Inheritance)
目的一起构建多个模块复用父 pom 的配置
配置位置父 pom 的 <modules>子 pom 的 <parent>
方向父「知道」有哪些子子「知道」自己的父
能否独立可以只聚合不继承可以只继承不聚合
常见用法统一构建整个项目统一依赖版本、插件配置

⚠️ 聚合和继承是两个独立的机制,可以分开用,但实际项目里常一起用——父 pom 既用 <modules> 聚合子模块(统一构建),又被子模块用 <parent> 继承(复用配置)。它们的方向相反:聚合是「父指向子」(父列出 modules),继承是「子指向父」(子指定 parent)。别把它们当成一回事——一个管「一起构建」,一个管「配置复用」。

完整版教学

一、为什么要多模块:拆分大项目

一个稍大的项目如果全塞在一个模块里,会有很多问题——代码耦合、无法复用、构建慢、多人协作冲突多。多模块把它按职责拆成独立的子模块

单模块的问题:
  所有代码在一个模块 → 层次混乱、无法单独复用某部分、改一点全量构建

多模块的好处:
  common 模块:放公共工具、实体、常量 → 被其他模块复用
  service 模块:业务逻辑 → 依赖 common
  web 模块:接口层 → 依赖 service
  → 分层清晰、可复用、可单独构建某模块、多人分模块并行开发

多模块的价值是「分而治之」——把大项目拆成职责单一、可独立构建、可相互依赖的小模块。这带来分层清晰(common/service/web 各司其职)、复用(common 被多处依赖)、构建效率(可只构建改动的模块)、协作友好(不同人负责不同模块)。理解「多模块是为了拆分和复用」,就理解了它要解决的问题。而拆分后如何「统一管理」和「统一配置」,就引出了聚合和继承。

二、聚合:统一构建多个模块

聚合(Aggregation) 解决「怎么把多个子模块作为一个整体统一构建」——不用一个个进子模块目录 mvn install,而是在父项目一次构建全部:

父 pom(packaging=pom)用 <modules> 列出子模块:
  <modules>
    <module>my-common</module>
    <module>my-service</module>
    <module>my-web</module>
  </modules>

在父项目执行 mvn install:
  Maven 会:
    1. 分析子模块之间的依赖关系(service 依赖 common、web 依赖 service)
    2. 按正确的依赖顺序构建:先 common → 再 service → 再 web(拓扑排序)
    3. 一次命令构建整个项目

聚合的核心价值是「一键构建整个项目 + 自动处理构建顺序」——你不用关心「先构建谁」,Maven 会根据模块间的依赖关系自动排序(reactor 机制,先构建被依赖的)。注意聚合项目的父 pom 必须是 packaging=pom(它不产出 jar,只是个「聚合器」)。理解「聚合 = 父 pom 用 modules 统一构建子模块、自动排序」,就理解了它的作用——把「分散的子模块」变成「可一键构建的整体」。

三、继承:复用父 pom 的配置

继承(Inheritance) 解决「多个子模块的公共配置怎么复用、不重复写」——子模块继承父 pom,把公共的依赖版本、插件、属性等一次配好、处处复用:

问题:每个子模块的 pom 都要配一堆相同的东西
  - 相同的依赖版本(spring-boot 版本、junit 版本...)
  - 相同的插件配置(compiler 插件的 Java 版本、打包插件...)
  - 相同的属性(编码、Java 版本...)
  → 每个子模块重复写、改一处要改 N 处

继承的解法:把公共配置放父 pom,子模块用 <parent> 继承:
  子模块从父 pom 继承:
    - dependencyManagement(依赖版本统一管理)★ 最常用
    - pluginManagement / build 插件配置
    - properties(属性)
    - dependencies(父声明的依赖,子自动拥有)

继承最重要的用途是 dependencyManagement(依赖版本统一管理)——父 pom 在 dependencyManagement 里声明所有依赖的版本,子模块引依赖时只写 groupId/artifactId、不写 version(版本从父 pom 统一取)。这样:① 版本统一(所有子模块用同一个 spring-boot 版本,不会冲突);② 改版本只改一处(改父 pom 的 dependencyManagement)。理解「继承 = 子模块用 parent 复用父 pom 配置、尤其统一依赖版本」,就理解了它怎么解决「多模块配置重复」的问题。

四、聚合与继承的区别:方向相反

这是本题最核心的辨析——聚合和继承是两个独立机制、方向相反、可分开用

聚合(父 → 子):
  父 pom 用 <modules> "列出"子模块 → 父"知道"有哪些子
  目的:统一构建(一起 build)
  方向:从父指向子

继承(子 → 父):
  子 pom 用 <parent> "指定"父 pom → 子"知道"自己的父
  目的:复用配置(继承 dependencyManagement/插件等)
  方向:从子指向父

关键区别:聚合是「父声明子」(父 pom 列 modules),继承是「子声明父」(子 pom 指 parent)——方向完全相反。而且它们逻辑上独立

可以只聚合不继承:父列出 modules,但子模块 parent 指向别的 pom(如 spring-boot-starter-parent)
可以只继承不聚合:子模块继承一个远程父 pom(如公司统一的 parent),但父 pom 没聚合它们

不过实际项目里绝大多数是「聚合 + 继承」一起用——同一个父 pom 既用 <modules> 聚合子模块(统一构建),又被子模块用 <parent> 继承(复用配置)。这样一个父 pom 同时扮演「聚合器」和「配置源」两个角色。理解「聚合父声明子管构建、继承子声明父管配置、方向相反可分开」,就彻底分清了这两个高频混淆点。

五、dependencyManagement 在多模块里的关键作用

多模块项目里,dependencyManagement 是统一依赖版本的核心,值得单独讲清它和 dependencies 的区别:

dependencies(直接引入依赖):
  子模块写在这里的依赖 → 真的被引入、参与编译打包

dependencyManagement(只管版本,不真引入):
  父 pom 写在这里的依赖 → 只是"声明版本",不真的引入
  子模块引这个依赖时 → 不用写 version(从父的 dependencyManagement 取)

效果:
  父 pom dependencyManagement 声明 spring-core 5.3.20
  子模块 A 引 spring-core(不写 version)→ 用 5.3.20
  子模块 B 引 spring-core(不写 version)→ 也用 5.3.20
  → 所有子模块版本统一,改版本只改父 pom 一处

这解决了多模块最头疼的「版本不一致」问题——如果每个子模块各写各的版本,很容易出现 A 用 spring 5.3、B 用 5.2 的冲突。用父 pom 的 dependencyManagement 统一管理,所有子模块共享同一套版本。这和 BOM(Bill of Materials) 是同一思想——BOM 就是一个只有 dependencyManagement 的 pom,用于统一一组相关依赖的版本(如 spring-boot-dependencies BOM)。理解「dependencyManagement 只管版本不真引入、统一多模块依赖版本」,就掌握了多模块依赖管理的核心。

六、多模块的构建与实践

多模块项目的构建和实践要点:

构建命令:
  在父项目根目录 mvn install → 构建所有子模块(reactor 自动排序)
  mvn install -pl my-service → 只构建指定模块(-pl = projects list)
  mvn install -pl my-service -am → 构建指定模块及其依赖的模块(-am = also make)

模块划分原则:
  按职责/层次分(common/service/web),不要过度拆分(模块太多也难管)
  被依赖的放底层(common),依赖别人的放上层(web)
  避免循环依赖(A 依赖 B、B 又依赖 A → Maven 报错)

实践建议:① 合理划分模块(按职责和层次,别过度拆分——模块太多反而增加管理成本);② 依赖方向单向(底层模块被上层依赖,不能循环依赖,否则 Maven reactor 无法排序、直接报错);③ 版本统一用父 pom 的 dependencyManagement④ 善用 -pl/-am 参数只构建需要的模块(大项目全量构建慢)。多模块是中大型 Java 项目的标准组织方式(Spring 自己就是多模块),掌握它对理解项目结构很重要。理解「合理拆分 + 单向依赖 + 统一版本 + 按需构建」,就能正确组织和构建多模块项目。

记忆钩子:「Maven 多模块拆大项目为子模块(common/service/web,分层复用);聚合(父 pom 用 <modules> 列子模块、统一构建、自动按依赖排序,父必须 packaging=pom);继承(子 pom 用 <parent> 复用父配置、尤其 dependencyManagement 统一依赖版本);两者方向相反(聚合父→子、继承子→父)、可分开但常一起用」

七、常见误区与追问

  • 误区:聚合和继承是一回事。 是两个独立机制、方向相反——聚合是父 pom 用 modules 列子模块(管统一构建),继承是子 pom 用 parent 复用配置(管配置复用);可分开用但常一起用。
  • 误区:多模块的父 pom 会打成 jar。 父/聚合 pom 必须是 packaging=pom(不产出 jar,只是聚合器和配置源);产出 jar 的是子模块。
  • 误区:dependencyManagement 会真的引入依赖。 不会——它只声明版本、不真引入;子模块要在 dependencies 里引才真引入(引时不用写 version,从父的 dependencyManagement 取)。
  • 误区:子模块之间可以循环依赖。 不行——Maven reactor 靠依赖关系排序构建,循环依赖(A→B、B→A)无法排序,直接报错;模块依赖必须单向。
  • 追问:聚合和继承的方向有什么不同? 聚合是「父声明子」(父 pom 的 modules 列出子模块,从父指向子);继承是「子声明父」(子 pom 的 parent 指定父,从子指向父),方向完全相反。
  • 追问:多模块怎么统一依赖版本? 用父 pom 的 dependencyManagement 声明所有依赖版本,子模块引依赖时不写 version(从父统一取),改版本只改父 pom 一处;和 BOM 是同一思想。
  • 追问:只想构建某个子模块及其依赖怎么办?mvn install -pl my-service -am——-pl(projects list)指定模块、-am(also make)连带构建它依赖的模块,避免全量构建。

八、加强记忆

Maven 多模块项目把大项目拆成职责单一、可独立构建又相互依赖的子模块(如 common/service/web,实现分层、复用、并行开发)。核心是两个易混但独立、方向相反的机制:① 聚合(Aggregation)——父 pom(必须 packaging=pom)用 <modules> 列出子模块mvn install 父项目时按依赖关系自动排序(reactor)统一构建所有子模块,方向是「父声明子」,管「一起构建」;② 继承(Inheritance)——子 pom 用 <parent> 指定父 pom复用父 pom 的配置(尤其 dependencyManagement 统一依赖版本——父声明版本、子引依赖时不写 version,避免多模块版本冲突、改版本只改一处,和 BOM 同思想),方向是「子声明父」,管「配置复用」。两者可分开用但实际常一起用(同一父 pom 既聚合又被继承)。实践:合理划分模块(别过度拆)、依赖单向(不能循环,否则 reactor 报错)、版本用 dependencyManagement 统一、用 -pl/-am 按需构建。一句话「多模块拆项目、聚合父声明子管构建(modules)、继承子声明父管配置(parent+dependencyManagement)、方向相反常一起用」。