Maven 作为 Java 项目的“官方标配”,一开始确实让人觉得优雅又科学。可一旦项目一大、多模块一多,坑也开始显现,尤其是依赖传递、父子模块和依赖聚合这些事儿。如果你和我一样,经历过“明明B1、B2都引了,A还是报依赖错”的崩溃,那咱今天就坐下来,聊聊背后的那些本质,以及最靠谱的工程实践。
1. 多模块结构,聚合和依赖不是一回事
很多项目会搞成这种结构:
A -- 业务模块
│
└── B -- 公共模块父
├── B1 -- 公共库1
└── B2 -- 公共库2
大部分人(包括过去的我)都会下意识觉得:
“B聚合了B1/B2,A依赖B,那B1/B2的依赖肯定能传上来啊!”
现实是:你想多了!
- Maven里
<modules>只是聚合(build aggregation) ,方便你一行命令编译所有子模块。 - 依赖传递(dependency transitivity)是通过
<dependencies>标签声明的! <packaging>pom</packaging>的父模块,只负责聚合和统一依赖版本(dependencyManagement),不参与依赖传递!
也就是说:
A想用B1、B2的东西,必须在
pom.xml里老老实实依赖B1/B2,别指望“父模块”给你带娃。
2. <packaging>pom</packaging>到底该怎么写?
说白了,pom类型的父模块就是个“大管家”:
- 负责管理所有子模块(
<modules>)。 - 用
<dependencyManagement>来统一依赖和插件的版本号,让全家老小不用吵架。
正确姿势
<packaging>pom</packaging>
<modules>
<module>B1</module>
<module>B2</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 只统一版本,不自动引入依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
</dependencyManagement>
父pom一般不写<dependencies> ,更不会被业务模块直接依赖。
3. 想偷懒?把B变成jar做依赖中转,靠谱吗?
很多团队会尝试这么玩:
- 把
B的<packaging>改成jar B的<dependencies>里引入B1、B2和公共依赖- A模块只依赖B,所有依赖全都“打包票”带上来
这么做,A确实可以自动获得B1/B2的所有依赖……但:
这种做法的优缺点
优点
- 外部模块只需依赖一个B,方便,没那么多乱七八糟的依赖声明。
- 对于小型项目、业务一体化强的模块群组,能省事。
缺点
- 依赖膨胀:A不一定用B1、B2所有东西,却全被拉进来。
- 维护地狱:B只是“壳子”,实际没内容,容易让后来人迷惑。
- 容易冲突:B1、B2有重名但不同版本依赖,A只会选一种,踩坑几率大增。
- 破坏“最小依赖”原则:只需用哪个就依赖哪个,不该一锅端。
说白了,就是偷懒一时爽,维护火葬场。
4. 业界推荐:父pom只聚合和管理,依赖自己声明
最佳实践永远都是这句:
聚合归聚合,依赖归依赖。需要哪个模块,自己加依赖声明。公共依赖用父pom的dependencyManagement统一版本就好。
这样,项目结构才清晰,依赖才可控。
后期加功能、拆模块、做服务治理,都不会被一锅端的“中转依赖”坑哭。
5. 小结 & 心得
一句话总结:
别把聚合当依赖,别把父pom当依赖中转。想要哪个依赖,就明明白白写出来。
搞清楚Maven父子、依赖、聚合三者的关系,多模块项目的“神坑”你就能避开大半。
有空跑下mvn dependency:tree看看自己的依赖树,能帮你提前发现问题。
希望这篇小结能帮你少走点弯路。如果你有自己的血泪史、或者还有什么搞不懂的点,欢迎留言一起吐槽!