先还原一个场景:项目是一个标准多模块 Maven 工程,外层一个父 pom,下面挂着 common、dao、service、web 这几个子模块。你在 IDEA 里想一次性把它们全部打包,于是盯上 Maven 工具窗口最顶上那个父节点,双击 package,控制台唰唰输出,最后一行显示 BUILD SUCCESS。然后你满怀期待地去 target 目录翻,结果父项目的 target 里没有业务 jar,部分子模块也没有生成对应产物。很多人这时候就会抛出标题里这句话:IDEA 对父项目打包时,不会打包下面的子项目。
每次看到类似问题我都会习惯性反问一句:你说的“打包”,是希望一堆子模块被逐个构建出来,还是希望父项目最终产出的那个包里包含子模块的 class 和配置?这两种诉求看起来差不多,实际解决路径完全不同。下面从 Maven 和 IDEA 的基本行为说起,把父 pom 配置、多模块聚合逻辑、fat jar 与 Spring Boot 的 repackage 触发时机,以及 IDEA 的操作入口差异一次理清楚。
1. 先搞清楚:父项目“打包”这个动作,在不同 packaging 下命运完全不同
1.1 packaging=pom 的父模块更像调度中心,不是装货柜
Maven 多模块项目里有两个容易混淆的层级关系:聚合和继承。聚合靠父 pom 里的 <modules> 把子模块目录列出来,让 Maven 知道“这次构建要带上哪些项目”;继承靠子 pom 里的 <parent> 回指父 pom,让子模块共享父 pom 里的依赖版本、插件配置和公共属性。
如果父 pom 的 <packaging> 写的是 pom,这个模块在 Maven 看来主要是个管理容器。它没有 src/main/java,所以正常情况下不会像 jar 或 war 那样进入 Java 编译和产物打包流程。它的职责是读取 <modules>,把所有子模块纳入同一个 reactor 构建批次,再按模块间的依赖顺序逐个构建。
这也是“父项目打包不会打包下面的子项目”这句话最容易产生的误解:你看到的父项目 target 里没有东西,不代表子模块没被打包,而是因为父模块本身就没有普通 jar 产物。真正的产物都应该在“各自子模块”的 target 目录下:
text复制parent-demo/
├── pom.xml
├── common/
│ ├── pom.xml
│ └── target/
│ └── common-1.0.0.jar
└── web/
├── pom.xml
└── target/
└── web-1.0.0.jar
如果你的父 pom 只是做依赖版本管理和模块聚合,那它不产出业务 jar 是完全正常的。我说句实在话,至少三分之一求助“父项目不打包子项目”的人,问题根源只是没有去子模块自己的 target 目录找产物。
1.2 modules 是“聚合”,parent 是“继承”,这是两套机制
很多人在项目里只写了子 pom 的 <parent>,觉得只要子模块认了父模块,父 pom 打包时就会自动带上它。这个理解不完整。子模块写 <parent>,决定的是它能继承父 pom 里的配置和依赖版本;但父 pom 里如果没有通过 <modules> 声明这个子模块,Maven 执行父项目构建时根本不知道有这么一个模块存在。
一个典型的标准配置是这样:
父 pom.xml:
xml复制<groupId>com.demo</groupId>
<artifactId>parent-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>server</module>
</modules>
common 模块的 pom.xml:
xml复制<parent>
<groupId>com.demo</groupId>
<artifactId>parent-demo</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>common</artifactId>
这里最容易写错的一点是:<module> 标签里填的是子模块的“目录名”,不是子模块的 artifactId。如果你在 IDEA 里手动改过模块目录名,或者从别人的项目里复制 modules 片段,经常会把 <module>common</module> 写成 <module>com.demo:common</module> 之类,Maven 就会找不到模块路径,构建结果自然对不上。
1.3 父 pom 自己也想产 jar 时,默认仍然不会把子模块吞进来
还有一种场景:父 pom 的 <packaging> 不是 pom,而是 jar,它自己也维护着一份公共业务代码,同时又通过 <modules> 管理子模块。这时候对父模块执行 package,父模块会作为一个普通 jar 模块被编译打包,它的 target 下确实会出现一个父模块自己的 jar。但请注意,这个 jar 里只会有父模块自身的代码,不会自动把 common、server 这些子模块的 class 也合并进来。
原因是 Maven 默认是按模块独立编译、独立打包的。<parent> 建立的是配置继承关系,<modules> 建立的是构建编排关系,它们都不会触发“把子模块代码物理合并进父 jar”的效果。想让父 jar 里带上子模块内容,必须引入能展开依赖的打包插件,这是下文要重点展开的部分。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 想让“最终包”带上子项目,得先分清五种诉求
2.1 诉求一:只想一次构建完所有子模块
这应该是大多数多模块项目的基本诉求。操作上很简单:父 pom 的 <packaging> 设为 pom,<modules> 里声明所有子模块,然后从父项目目录执行构建。
命令行方式:
bash复制mvn clean package
如果你想连子模块一起安装到本地仓库,让本地其他项目能通过坐标引用这些模块,那就用 install:
bash复制mvn clean install
这里插一个高频认知误差:package 阶段只把当前 reactor 里各模块的 jar/war 生成到各自 target 目录,不会主动安装到本地 Maven 仓库。如果后续某个独立项目直接通过 com.demo:common:1.0.0 引用 common 模块,而 common 模块只执行过 package 没执行过 install,Maven 就会报依赖找不到。这不是“打包逻辑坏了”,而是生命周期没走完。IDEA 的 Maven 面板里,很多有经验的开发者默认双击的是 install 而不是 package,不是因为 package 不对,而是因为多模块联调场景下 install 更省事。
