news 2026/11/3 9:21:07

Maven依赖管理:dependencies、dependencyManagement与BOM分别改变了什么

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Maven依赖管理:dependencies、dependencyManagement与BOM分别改变了什么

父POM已经管理了一个库的版本,子模块里却仍然没有这个依赖;把配置移到父POM的dependencies,子模块没写依赖也出现了它。这两种结果都可能是正确的,关键是先分清:声明要用什么,和管理用了以后采用什么配置,是两件事。

这次用Maven 3.9.12、JDK 21.0.2做了一个最小模型实验。只保留父POM的dependencyManagement时,子项目的有效POM没有直接依赖;子项目再声明同一个库,版本才补成1.4。整个过程不需要Spring项目,也不需要用一个大型依赖树掩盖配置关系。

2026-11-02修订:补充可复现的有效POM对照,纠正scope与打包的关系,并明确BOM导入和父POM继承的边界。

同一个坐标,先看它出现在哪里

dependencies里的声明参与构建项目的依赖关系。这个声明可以来自当前POM,也可以从父POM继承;此外还可能通过传递依赖引入其他库。

dependencyManagement提供的是依赖配置管理。它不会单凭一条管理记录就给当前项目增加依赖,但会影响匹配依赖的版本、scope等信息,也能控制依赖图中匹配的传递依赖版本。把它叫“版本清单”方便入门,不能理解成它只是一段不参与解析的文档。

创建两个目录,先保存parent-managed/pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>example.fixture</groupId> <artifactId>parent-managed</artifactId> <version>1.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>demo-lib</artifactId> <version>1.4</version> </dependency> </dependencies> </dependencyManagement> </project>

再保存managed-use/pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>example.fixture</groupId> <artifactId>parent-managed</artifactId> <version>1.0</version> <relativePath>../parent-managed/pom.xml</relativePath> </parent> <artifactId>managed-use</artifactId> <packaging>pom</packaging> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>demo-lib</artifactId> </dependency> </dependencies> </project>

在两目录的上一级执行:

$effectiveFile = Join-Path $PWD 'effective-managed.xml' mvn -B -ntp -f managed-use/pom.xml org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom "-Doutput=$effectiveFile"

effective-pom把继承、管理等处理后的项目模型写成XML。Help Plugin文档说明了输出参数;这里固定插件版本,避免命令简写的插件解析随环境变化。

本次生成的顶层dependencies中,example.fixture:demo-lib的版本是1.4。然后分别改变一处配置,得到以下结果:

配置子项目有效POM中的直接依赖
父POM只管理版本,子项目没有声明依赖没有demo-lib
父POM管理1.4,子项目声明demo-lib并省略版本demo-lib:1.4
父POM在普通dependencies声明demo-lib:1.4,子项目不再声明继承demo-lib:1.4
父POM管理1.4,子项目在直接依赖上明确写4.0demo-lib:4.0

这里的坐标是人工示例,实验观察的是项目模型,没有解析这个库的JAR,也没有编译业务代码。effective-pom成功不代表对应版本一定存在,更不代表它可以与项目其他库一起正常运行。

最后一行也说明,dependencyManagement不是无法绕过的锁。直接依赖显式写出的版本可以覆盖管理值。团队若要禁止越界版本,需要另外的规则和检查,不能只看父POM里写过一个版本。

图1:分别看有没有直接依赖、采用哪个版本。管理不会自动注入,直接声明也可以覆盖管理版本。

scope控制可见性,不能直接等同于“打进JAR”

先修正一个常见说法:普通Maven JAR项目执行打包,并不会自动把所有compile依赖的类塞进生成的JAR。依赖的classpath范围,与最终如何组装产物,需要分开看。

scope主代码编译测试编译/执行应用运行classpath
compile,默认包含包含包含
provided包含包含不由这项依赖加入
runtime不包含包含包含
test不包含包含不包含

provided常用于明确由运行环境提供的API,前提是你的部署环境确实提供它。WAR、Spring Boot重打包、Shade等组装方式对依赖的处理还各有规则;不能仅凭scope就断言某个最终文件里一定有或一定没有某个JAR。system依赖本地路径,通常会增加可移植性问题;import则用于导入管理模型,不属于普通运行classpath。范围定义见Maven依赖机制说明。

BOM导入的是哪一部分

项目已经有父POM,但又希望使用另一套依赖版本管理,可以在dependencyManagement中导入BOM:type=pom,scope=import。它没有给项目增加第二个父POM。

为了把导入边界看清,下面的demo-bom/pom.xml故意多放了一个普通依赖、一个属性标记和一个插件管理标记。它们只用于观察哪些内容会进入消费者,不是建议把BOM设计成这样:

<project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>example.fixture</groupId> <artifactId>demo-bom</artifactId> <version>1.0</version> <packaging>pom</packaging> <properties> <demo.version>2.0</demo.version> <bom.marker>not-inherited</bom.marker> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>demo-lib</artifactId> <version>${demo.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>bom-only-dep</artifactId> <version>9.9</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>example.fixture</groupId> <artifactId>bom-only-plugin</artifactId> <version>9.9</version> </plugin> </plugins> </pluginManagement> </build> </project>

消费者保存为import-use/pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>example.fixture</groupId> <artifactId>import-use</artifactId> <version>1.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>demo-bom</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>example.fixture</groupId> <artifactId>demo-lib</artifactId> </dependency> </dependencies> </project>

将这个自建BOM安装到示例专用仓库,再生成消费者的有效POM:

$demoRepository = Join-Path $PWD '.demo-repository' $effectiveBomFile = Join-Path $PWD 'effective-import.xml' mvn -B -ntp "-Dmaven.repo.local=$demoRepository" -f demo-bom/pom.xml org.apache.maven.plugins:maven-install-plugin:3.1.3:install mvn -B -ntp "-Dmaven.repo.local=$demoRepository" -f import-use/pom.xml org.apache.maven.plugins:maven-help-plugin:3.5.2:effective-pom "-Doutput=$effectiveBomFile"

第一次执行需要下载官方插件。这里安装的只是示例自己的POM,没有向远程仓库发布任何东西。查看effective-import.xml,本次结果是:

  • 顶层直接依赖只有demo-lib:2.0,BOM里的${demo.version}已用于求出这个版本。
  • bom-only-dep没有作为消费者的直接依赖出现。
  • 消费者没有继承bom.marker属性,也没有导入bom-only-plugin的插件管理。
  • 删除消费者的普通dependencies以后,导入的管理记录仍在,顶层直接依赖为空。

导入不是整份POM继承。它引入目标的依赖管理内容,不会顺便拿到目标的构建配置;即便目标BOM使用属性组织版本,也不能把导入理解成那些属性都成为了消费者自己的属性。官方Importing Dependencies示例展示了这项机制。

本次另一个对照是在消费者自己的dependencyManagement里明确管理demo-lib:3.0,同时保留BOM导入;最终直接依赖采用3.0。需要覆盖BOM版本时,把覆盖写在消费者自己的管理项中,比依赖对方POM内部的属性名更容易审查。

图2:import导入BOM的依赖管理内容,不把普通依赖、属性标记或插件管理一起继承给消费者。

依赖还不对时,检查匹配和实际路径

没有父管理、没有BOM,又在直接依赖中省略版本,本次mvn validate就报出:

'dependencies.dependency.version' for example.fixture:demo-lib:jar is missing.

管理项还需要匹配依赖身份。通常只写groupId和artifactId就够,是因为默认type为jar、classifier为空;完整匹配还包括type和classifier。本次只管理默认jar,把使用处改成type=war并省略版本,得到的仍是缺少版本错误,不会因为G、A相同就自动套用。

如果问题来自传递依赖,先确认是谁把它带进来。Maven通常按更近的路径选择版本,同深度再看先声明者;匹配的依赖管理还能影响传递版本。exclusions排除的是某条依赖路径下面的传递依赖,并不是从父POM继承的整个直接依赖列表中任意删除一项。另一路仍然引入同一库时,它也可能继续留在依赖图中。

因此,排查时先用有效POM确认继承与管理结果,再用dependency:tree观察真实解析路径;后者需要对应构件可解析,不能拿本文的人工坐标直接当作真实库。

BOM能集中表达发布者选择的一组版本,Maven本身不会替发布者证明这些版本已经兼容。升级后仍要跑应用的构建和关键测试。把依赖管理做好,解决的是版本选择有出处、覆盖可追踪;运行兼容性还要由实际验证回答。

接着可以看父POM、聚合POM与BOM的关系:同样写着packaging=pom,三者建立关系的方式并不相同。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 9:20:12

【Flink实战】Flink网络内存和托管内存

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/3 9:19:56

【Flink实战】Flink -C实现类路径配置与实现UDF Jar

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/3 9:17:20

项目八 防火墙基本配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/3 9:16:06

为什么选择PP-LCNet_x1_0_textline_ori_onnx:高效文本方向检测的7大优势

为什么选择PP-LCNet_x1_0_textline_ori_onnx&#xff1a;高效文本方向检测的7大优势 PP-LCNet_x1_0_textline_ori_onnx是飞桨PaddlePaddle推出的轻量级文本方向检测模型&#xff0c;专为高效识别文本行方向&#xff08;0度或180度&#xff09;设计。该模型基于PP-LCNet架构优化…

作者头像 李华
网站建设 2026/11/3 9:14:35

eslav_PP-OCRv5_mobile_rec_onnx后处理技术:CTCLabelDecode算法原理与应用

eslav_PP-OCRv5_mobile_rec_onnx后处理技术&#xff1a;CTCLabelDecode算法原理与应用 在OCR&#xff08;光学字符识别&#xff09;技术中&#xff0c;后处理是将模型输出的原始数据转换为可读文本的关键步骤。eslav_PP-OCRv5_mobile_rec_onnx作为轻量级的移动端文字识别模型&…

作者头像 李华