Maven 最佳实践
会写 pom.xml 不等于会用 Maven。本文聚焦工程实战:版本怎么统一管、多模块怎么组织、构建怎么提速,以及那些几乎每个团队都踩过的坑。
用 BOM 统一管理版本
BOM(Bill of Materials)是一个只声明依赖版本的特殊 POM,通过 dependencyManagement 加 import 作用域引入。Spring Boot、Spring Cloud 都提供官方 BOM:
<dependencyManagement> |
导入后,BOM 覆盖范围内的依赖(如 Jackson、Netty)都不用写版本号,且各组件版本经过官方兼容性验证,省去手工拼版本的试错成本。自定义内部框架也建议发一个 BOM,让业务方一行导入。
dependencyManagement vs dependencies
这是高频混淆点:
dependencies:真实引入依赖,子模块会继承。dependencyManagement:只声明版本和 scope 的默认值,不引入任何依赖。子模块仍需显式写groupId + artifactId,但可以省略 version。
<!-- 父 POM:只管版本 --> |
配套的还有 pluginManagement,同样只锁插件版本不启用插件。另一个重要用途是强制锁定传递依赖版本:在 dependencyManagement 里声明的版本优先级高于传递仲裁结果,是修冲突的正规手段。
多模块项目组织
多模块项目 = 一个父 POM(packaging 为 pom)+ 若干子模块。父 POM 的职责收敛到三件事:声明 <modules>、集中管理依赖与插件版本、定义公共属性。业务依赖只在用到的模块里声明,别把父 POM 堆成大杂烩——所有子模块会被动继承 dependencies,无关依赖一旦写进父 POM,每个模块都会背上。
模块划分按职责边界来:Dubbo 拆出 dubbo-common、dubbo-remoting、dubbo-rpc 就是范例。划分得当的收益是降低耦合(jar 级边界比包级更难逾越)、复用性提升、构建可以按模块增量进行。
明确声明编译与插件版本
两件事必须显式写进 POM,不能依赖默认值:
<properties> |
maven.compiler.release 对应 javac --release,比分开配 source/target 更可靠——它会同时约束字节码版本和可用的 JDK API,防止误用新版 API 却打着旧版 target。版本号写 8、17、21 这种格式,别再用 1.8。Maven Compiler Plugin 3.13.0 起在 JDK 8 上也能识别 release 并自动转换,更旧的插件才需要退回 source/target。
插件版本同理。Maven 核心插件的默认版本跟着 Maven 发行版走,换 Maven 版本构建行为就可能漂移。项目用到的每个插件(compiler、surefire、shade……)都应显式声明 version,多模块项目统一放进父 POM 的 pluginManagement。
构建加速
几个立竿见影的手段:
# 并行构建:按模块数开线程,1C 表示每核一个线程 |
下载慢则换镜像。在 settings.xml 里配置阿里云镜像替代中央仓库:
<mirror> |
CI 上再叠一层本地仓库缓存(GitHub Actions 的 cache: "maven"),避免每次全量下载。
常见坑
- 不指定编译版本:依赖插件默认值,换台机器行为就变。显式配置
maven.compiler.release(JDK 9+ 对应javac --release),Java 17 就写17,别再写1.8风格的旧格式。 - 不锁插件版本:Maven 核心插件的默认版本随 Maven 版本漂移,构建不可重现。所有用到的插件都显式写 version。
- 依赖 SNAPSHOT 上生产:快照版每次构建可能拉到不同产物,正式发布必须全部用固定版本。
- 靠
mvn install出包:CI 里推荐./mvnw -B clean verify,install会污染本地仓库,只有后续步骤确实依赖本地安装结果时才用。 - 不提交 Maven Wrapper:提交
mvnw、mvnw.cmd和.mvn/wrapper/目录,新人和 CI 无需预装 Maven,且版本与项目声明一致。 - Profile 滥用:Profile 适合做环境参数开关(dev/prod),不适合制造两套完全不同的构建——那意味着你永远只测过其中一半。
小结
- 版本管理三板斧:官方 BOM 打底、dependencyManagement 锁版本、properties 收敛自定义版本号。
- dependencyManagement 只声明不引入,真实依赖仍需子模块显式声明。
- 多模块父 POM 保持精简,依赖按需下沉到子模块。
- 提速组合:
-T并行、-pl/-am局部构建、镜像仓库、CI 缓存。 - 显式指定编译版本与插件版本、提交 Wrapper、生产禁用 SNAPSHOT,是构建可重现的底线。

