Maven多模块依赖“那些坑”与最佳实践:一杯咖啡聊聊我的血泪史

157 阅读3分钟

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做依赖中转,靠谱吗?

很多团队会尝试这么玩:

  1. B<packaging>改成jar
  2. B<dependencies>里引入B1、B2和公共依赖
  3. A模块只依赖B,所有依赖全都“打包票”带上来

这么做,A确实可以自动获得B1/B2的所有依赖……但:

这种做法的优缺点

优点

  • 外部模块只需依赖一个B,方便,没那么多乱七八糟的依赖声明。
  • 对于小型项目、业务一体化强的模块群组,能省事。

缺点

  • 依赖膨胀:A不一定用B1、B2所有东西,却全被拉进来。
  • 维护地狱:B只是“壳子”,实际没内容,容易让后来人迷惑。
  • 容易冲突:B1、B2有重名但不同版本依赖,A只会选一种,踩坑几率大增。
  • 破坏“最小依赖”原则:只需用哪个就依赖哪个,不该一锅端。

说白了,就是偷懒一时爽,维护火葬场


4. 业界推荐:父pom只聚合和管理,依赖自己声明

最佳实践永远都是这句:

聚合归聚合,依赖归依赖。需要哪个模块,自己加依赖声明。公共依赖用父pom的dependencyManagement统一版本就好。

这样,项目结构才清晰,依赖才可控。
后期加功能、拆模块、做服务治理,都不会被一锅端的“中转依赖”坑哭。


5. 小结 & 心得

一句话总结:

别把聚合当依赖,别把父pom当依赖中转。想要哪个依赖,就明明白白写出来。

搞清楚Maven父子、依赖、聚合三者的关系,多模块项目的“神坑”你就能避开大半。
有空跑下mvn dependency:tree看看自己的依赖树,能帮你提前发现问题。


希望这篇小结能帮你少走点弯路。如果你有自己的血泪史、或者还有什么搞不懂的点,欢迎留言一起吐槽!