Maven 详解(下)

在标准的 Maven 多模块项目中,<parent><modules> 不仅可以同时出现,它们是"黄金搭档",同时存在才能完整实现父子工程的聚合与继承功能。

这两个标签共同构成了 Maven 多模块项目的骨架。理解它们的作用、位置和工作机制是掌握模块化开发的关键。

一、 <modules> 标签 (在父 POM 中) - 聚合 (Aggregation)

  • 作用 : 告诉 Maven:"我这个项目(父工程)是由哪些子模块组成的"。它实现了项目聚合

  • 位置 : 父工程 (packaging=pom) 的 pom.xml 文件中

  • 语法:

    复制代码
    <modules>
        <module>relative/path/to/module1</module>
        <module>relative/path/to/module2</module>
        <!-- ... -->
    </modules>
  • <module> 元素的值:

    • 这是一个相对路径,指向子模块目录的位置。
    • 路径是相对于父 POM 文件所在目录的。
    • 常见形式 :
      • <module>module-common</module> (子模块与父 POM 同级)
      • <module>../shared-lib</module> (子模块在父工程的上一级目录,较少见)
      • <module>services/user-service</module> (更深层级的目录结构)
    • 路径指向的是包含 pom.xml 文件的子模块目录,而不是文件本身。
  • 工作原理 (聚合机制):

    1. 当你在父工程根目录 执行 Maven 命令时(例如 mvn clean install),Maven 首先会读取父 POM。
    2. Maven 发现 <modules> 标签,就知道这是一个聚合项目。
    3. Maven 会遍历 <modules> 列表中的每一个路径,找到对应的子模块 pom.xml
    4. Maven 分析所有子模块之间的依赖关系 (通过查看每个子模块 pom.xml 中的 <dependencies>)。
    5. Maven 根据依赖关系自动计算出一个正确的构建顺序(有依赖的模块必须先构建)。
    6. Maven 按照计算出的顺序,依次对每个子模块执行指定的生命周期阶段。
  • 关键点:

    • 聚合是可选的 :你可以有一个父 POM 用于继承,但不包含 <modules>,这样你就不能在父目录下用一条命令构建所有模块。
    • 聚合不等于继承<modules> 只负责"把哪些模块一起构建",它本身不提供任何配置继承 。继承是由 <parent> 标签实现的。
    • 独立性 :即使没有 <modules>,你也可以单独进入某个子模块目录运行 mvn 命令来构建它。

二、 <parent> 标签 (在子 POM 中) - 继承 (Inheritance)

  • 作用 : 告诉 Maven:"我的这个子模块要继承哪个父 POM 的配置"。它实现了配置继承

  • 位置 : 每个需要继承的子模块的 pom.xml 文件中

  • 语法:

    复制代码
    <parent>
        <groupId>com.example</groupId>
        <artifactId>my-parent-project</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <!-- 相对路径,告诉 Maven 如何找到父 POM 文件 -->
        <relativePath>../pom.xml</relativePath>
    </parent>
  • 各个元素的含义:

    • <groupId>, <artifactId>, <version>: 这三个元素精确地定位了父 POM 的坐标。Maven 会根据这三个信息去查找父 POM。
    • <relativePath>:
      • 最重要的属性之一
      • 它是一个相对路径 ,告诉 Maven 在哪里可以找到父 POM 的 pom.xml 文件。
      • 路径是相对于当前子模块的 pom.xml 文件所在目录的。
      • 默认值 : ../pom.xml。这意味着 Maven 默认假设父 POM 在子模块目录的上一级目录。
      • 何时需要显式声明 :
        • 当你的标准结构被打破时(例如,子模块不在父工程目录下)。
        • 为了代码清晰,建议总是显式写出 <relativePath>
        • 如果父 POM 已经发布到远程仓库,而你希望优先从仓库下载而不是找本地文件,可以设置 <relativePath/>(空值)。
  • 工作原理 (继承机制):

    1. 当 Maven 处理一个子模块的 pom.xml 时,发现 <parent> 标签。
    2. Maven 会根据 <groupId>, <artifactId>, <version><relativePath> 去定位并读取父 POM 文件。
    3. Maven 将父 POM 中的配置与子模块自身的配置进行合并
    4. 继承的内容包括
      • 父 POM 的 groupId (子模块通常会继承,除非自己声明)
      • 父 POM 的 version (子模块通常会继承,除非自己声明)
      • <properties>
      • <dependencyManagement>
      • <pluginManagement>
      • <repositories> / <pluginRepositories>
      • <build> 中直接定义的配置(如果父 POM 不是在 <pluginManagement> 下定义插件)。
    5. 子模块可以在自己的 pom.xml覆盖补充这些继承来的配置。
  • 关键点:

    • 继承是可选的:一个子模块可以选择不继承任何父 POM。
    • 单继承 : Maven POM 只支持单一父类继承(就像 Java 类一样)。一个子模块只能有一个 <parent>
    • 链式继承 : 继承可以形成链条。例如,Child -> Parent -> GrandParentChild 会继承 ParentGrandParent 的所有配置。
    • BOM 导入也是继承的一种应用 : Spring Boot 的 spring-boot-starter-parent 本质上就是一个提供了大量默认配置的父 POM。

三、 <parent><modules> 的协同工作

虽然 <parent><modules> 分别位于不同的 POM 文件中,但它们共同协作,使多模块项目得以高效运行。

典型工作流程:

  1. 结构建立:

    • 你在父工程的 pom.xml 中用 <modules> 列出了所有子模块。
    • 你在每个子模块的 pom.xml 中用 <parent> 指向了父工程。
  2. 构建触发:

    • 你在父工程根目录执行 mvn install
  3. Maven 执行过程:

    • 步骤 1 (聚合) : Maven 读取父 POM,发现 <modules>,知道了要构建 module-common, module-service, module-web
    • 步骤 2 (依赖分析) : Maven 解析每个子模块的 pom.xml,发现 module-web 依赖 module-servicemodule-service 依赖 module-common
    • 步骤 3 (排序) : Maven 计算出构建顺序:module-common -> module-service -> module-web
    • 步骤 4 (继承与构建) :
      • 开始构建 module-common。由于它的 pom.xml 中有 <parent>,Maven 加载父 POM 配置,合并后得到 Effective POM,然后执行 install
      • 构建 module-service。同样,通过 <parent> 继承父 POM 配置,并且它的依赖 module-common 已经在本地构建好了(在 ~/.m2/repository 或模块间直接引用),可以顺利编译。
      • 构建 module-web。同理,继承父 POM,并成功依赖已构建好的 module-service

四、 总结对比表

特性 <modules> <parent>
中文含义 模块 (复数) 父级
主要目的 聚合 (Aggregation) - "一起构建哪些模块" 继承 (Inheritance) - "从谁那里继承配置"
所在位置 父工程pom.xml 子工程pom.xml
定义方向 自上而下 (父告诉世界它有哪些孩子) 自下而上 (子告诉世界它的父亲是谁)
内容 包含一个或多个 <module> 元素,每个是子模块目录的相对路径 包含 <groupId>, <artifactId>, <version><relativePath>,用于定位父 POM
是否必需 对于聚合构建是必需的 对于配置继承是必需的
能否省略 可以省略,意味着不能一键构建所有模块 可以省略,意味着子模块不继承任何父配置

一句话概括

<modules>父工程聚合 子模块,用 <parent>子工程继承父工程的配置。两者结合,才能实现 Maven 多模块项目的统一管理和一键构建。

让我们通过一个具体的项目结构来说明:

复制代码
my-project/                 # 父工程根目录
├── pom.xml                 # 父 POM (包含 <modules>)
├── common/                 # 子模块1
│   └── pom.xml             # 子 POM1 (包含 <parent>)
├── service/                # 子模块2
│   └── pom.xml             # 子 POM2 (包含 <parent>)
└── web/                    # 子模块3
    └── pom.xml             # 子 POM3 (包含 <parent>)

1. 父 POM (my-project/pom.xml) - 包含 <modules>

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!-- ✅ 父模块使用 <modules> 来声明它的子模块 -->
    <modules>
        <module>common</module>
        <module>service</module>
        <module>web</module>
    </modules>

    <!-- ... 其他配置如 dependencyManagement, properties 等 ... -->
</project>

2. 子 POM (web/pom.xml) - 包含 <parent>

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <!-- ✅ 子模块使用 <parent> 来声明它的父模块 -->
    <parent>
        <groupId>com.example</groupId>
        <artifactId>my-project</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../pom.xml</relativePath> <!-- 指向父 POM -->
    </parent>

    <artifactId>web</artifactId>
    <!-- version 继承自父模块 -->

    <!-- ... 子模块自己的 dependencies, build 等配置 ... -->
</project>

如果缺少其中一个会怎样?

情况 结果
<modules> 但没有 <parent> 只有聚合,没有继承 。 • 你可以在父目录用 mvn install 一键构建所有模块。 • 但是 ,所有子模块无法继承父 POM 中定义的 dependencyManagementpropertiespluginManagement 等配置。每个子模块都需要自己重复定义这些公共配置,失去了统一管理的意义。
<parent> 但没有 <modules> 只有继承,没有聚合 。 • 所有子模块都可以继承父 POM 的配置,实现了统一管理。 • 但是 ,你无法 在父目录运行一条命令来构建所有模块。你必须手动进入每个子模块目录,依次执行 mvn install。这在模块数量多时非常低效。
两者都有 ✅ 完美的聚合与继承 。 • 在父目录运行 mvn clean install,Maven 会: 1. 通过 <modules> 知道有哪些子模块。 2. 通过 <parent> (在子 POM 中) 确保每个子模块都继承了公共配置。 3. 自动分析依赖关系并按正确顺序构建所有模块。 • 这是开发大型项目的最佳实践。

结论

  • <parent><modules> 不仅可以同时出现,而且是构建一个功能完整的 Maven 多模块项目所必需的。
  • 它们是互补 的关系,分别解决了配置继承项目聚合这两个核心问题。
  • <modules>父 POM 中定义,负责"组织"。
  • <parent>每个子 POM 中定义,负责"传承"。
  • 两者缺一不可。只有当它们"同时出现"时,Maven 的父子工程模式才能发挥出最大的威力,实现高效、统一的项目管理。
相关推荐
小坏讲微服务2 小时前
Docker-compose 搭建Maven私服部署
java·spring boot·后端·docker·微服务·容器·maven
inferno2 小时前
Maven基础(二)
java·开发语言·maven
杨武博2 小时前
关于maven中pom依赖冲突问题记录
java·maven
陈果然DeepVersion3 小时前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(十)
java·spring boot·ai·kafka·面试题·向量数据库·rag
但要及时清醒4 小时前
ArrayList和LinkedList
java·开发语言
一叶飘零_sweeeet4 小时前
从测试小白到高手:JUnit 5 核心注解 @BeforeEach 与 @AfterEach 的实战指南
java·junit
摇滚侠4 小时前
Spring Boot3零基础教程,Reactive-Stream 四大核心组件,笔记106
java·spring boot·笔记
Z3r4y4 小时前
【代码审计】RuoYi-3.0 三处安全问题分析
java·web安全·代码审计·ruoyi-3.0