dependency:tree 的依赖路径需关注三点:谁引入、版本被谁覆盖、是否重复引入;maven按“最近优先”策略仲裁,-dverbose显细节,-dincludes/-dexcludes精准过滤,须写在直接引入冲突的父依赖内,dependencymanagement仅提供版本规则而非实际引入,编译通过不保证运行时安全。

dependency:tree 显示的依赖路径到底怎么看
看到 dependency:tree 输出一堆缩进、带“\”和“+”的树状结构,第一反应是眼花——其实关键就看三样:谁引入了它、版本被谁覆盖了、有没有重复引入不同版本。Maven 采用“最近优先”(nearest definition)策略,不是最顶层的声明生效,而是离你项目 pom.xml 路径最短的那个声明起作用。
实操建议:
- 加
-Dverbose参数:显示被省略的仲裁细节,比如某版本因冲突被忽略的具体原因 - 用
-Dincludes=groupId:artifactId过滤特定库,避免信息过载(例如-Dincludes=org.slf4j:slf4j-api) - 配合
-Dexcludes=groupId:artifactId排除干扰项,比如排除所有 test 范围依赖 - 输出到文件再搜索:
mvn dependency:tree -Dverbose > tree.log,比在终端滚屏靠谱得多
exclude 标签写在哪、怎么写才真正生效
<exclusion></exclusion> 必须写在「主动引入冲突依赖的那个父依赖」下面,而不是写在你自己模块的顶级 <dependencies></dependencies> 里——这是最多人写错的位置。比如 spring-boot-starter-web 拉进了旧版 netty-buffer,你要排除它,就得在 spring-boot-starter-web 的 <dependency></dependency> 块内加 <exclusions></exclusions>,而不是单独再声明一次 netty-buffer 并 exclude 自己。
常见错误现象:
- 写了
<exclusion></exclusion>但dependency:tree里依然显示该依赖 → 检查是否 exclude 错了父依赖(比如去 exclude 了spring-boot-starter-data-redis,实际冲突来自spring-boot-starter-webflux) - exclude 后编译报
NoClassDefFoundError→ 被 exclude 的 artifact 其实被其他路径间接需要,得补一个显式、兼容的版本声明 - 用了通配符或模糊写法(如
groupId:*)→ Maven 不支持,<exclusion></exclusion>只接受精确的<groupid></groupid>和<artifactid></artifactid>
dependencyManagement 和直接 dependency 的优先级关系
<dependencymanagement></dependencymanagement> 不会引入依赖,只管“版本仲裁规则”。它像一份全局价格表,而 <dependencies></dependencies> 才是实际下单。当两者同时存在时:direct dependency 的 version 优先级高于 dependencyManagement 中同 GAV 的声明;但如果你的 direct dependency 没写 version,就会按 dependencyManagement 里的来。
性能 / 兼容性影响:
- 过度依赖
dependencyManagement统一版本,可能掩盖实际传递依赖的真实需求(比如 A 库要 2.8.x,B 库要 3.1.x,硬统一到 3.0.0 可能导致运行时异常) - Spring Boot 的
spring-boot-dependenciesbom 就是靠dependencyManagement实现版本锁定,但你自己的子模块如果显式声明了冲突版本,它就会被覆盖——这点常被误认为 “bom 失效” - 多模块项目中,父 pom 的
dependencyManagement对子模块有效,但子模块自己也能覆写,这种层级叠加容易漏看
mvn clean compile 之后还是运行时报 NoClassDefFound 或 MethodNotFound
编译通过 ≠ 运行时安全。Maven 编译阶段只校验 classpath 上有对应类签名,不检查方法是否存在或字节码兼容性。典型场景是:你强制指定新版 guava,但某个底层依赖(如老版 elasticsearch-rest-client)内部调用了已被移除的 Splitter.on(CharMatcher) 方法。
排查要点:
- 运行时报错的类名和方法,反向查它属于哪个 jar:
mvn dependency:tree -Dverbose | grep -A5 -B5 "guava" - 用
javap -cp xxx.jar com.google.common.base.Splitter看目标 jar 是否真含那个方法(注意 JDK 版本和字节码版本) - 别只信
dependency:tree的版本号,下载对应 jar 解压,直接看META-INF/MANIFEST.MF或反编译 class 文件确认实际内容 - 某些插件(如
spring-boot-maven-plugin)打包时会重排依赖顺序,java -cp直接跑和java -jar行为可能不同,务必在最终包环境下验证
真正麻烦的从来不是找到冲突,而是判断哪个版本能同时满足所有上游依赖的 API 要求——这时候往往得翻源码、看 release note,甚至手动 patch。










