目录

一、先明确核心前提:Maven依赖优先级的核心原则

二、单模块Maven依赖优先级(基础模式,必吃透)

2.1 模式1:直接依赖 > 传递依赖(核心优先级)

2.2 模式2:同一层级,声明顺序优先

2.3 模式3:依赖范围(scope)的优先级

3.1 compile(默认范围,优先级最高)

3.2 provided(次高优先级)

3.3 runtime(次低优先级)

3.4 test(最低优先级)

2.4 补充:system范围的特殊说明

三、多模块Maven依赖优先级(重点,实战高频)

3.1 模式1:子模块直接声明 > 父模块声明(覆盖优先)

父模块pom.xml(依赖管理):

子模块(xxx-service)pom.xml:

3.2 模式2:跨模块依赖,就近优先(路径越短,优先级越高)

3.3 模式3:子模块嵌套(子子模块),继承+覆盖优先级

四、Spring Boot BOM依赖管理优先级(实战重点,必懂)

4.1 BOM依赖的两种引入方式(优先级不同)

方式1:继承spring-boot-starter-parent(推荐,优先级高)

方式2:在dependencyManagement中引入spring-boot-dependencies(灵活,优先级低)

4.2 BOM依赖的核心优先级规则(实战必记)

4.3 BOM依赖避坑点(高频问题)

五、传递依赖的特殊优先级(避坑重点)

5.1 传递依赖的“路径长度优先”

5.2 传递依赖的“声明顺序优先”

5.3 排除传递依赖(打破优先级)

六、依赖优先级实战避坑+冲突解决技巧

6.1 高频避坑点(必记)

6.2 依赖冲突解决技巧(实战必备)

七、总结(实战重点回顾)


🤵‍♂️ 个人主页:Java开发与君同行

✍🏻作者简介:Java学习者
🐋 希望大家多多支持,我们一起进步!😄
如果文章对你有帮助的话,
欢迎评论 💬点赞👍🏻 收藏 📂加关注+

前言:作为Java程序员,我们日常开发中几乎每天都在和Maven依赖打交道——引入依赖、排查依赖冲突、调整依赖版本,而这一切的核心,都离不开「依赖优先级」。很多开发者遇到“依赖冲突、明明引入了依赖却找不到类、多模块中依赖版本不一致、Spring Boot BOM依赖不生效”等问题,本质上都是没吃透Maven依赖优先级的规则。

Maven依赖优先级并非杂乱无章,而是有明确的规则体系,涵盖「单模块依赖、多模块依赖、依赖范围、传递依赖、Spring Boot BOM依赖继承」等所有高频场景。本文结合多年Java实战经验,从“核心优先级规则、单模块场景、多模块场景、传递依赖优先级、BOM依赖管理优先级、依赖冲突解决”6个核心维度,拆解所有依赖优先级模式,搭配实操案例和避坑技巧,全程干货无空洞理论,新手能快速吃透,老鸟可查漏补缺,完全适配CSDN技术文章“实战+避坑”的核心风格。

提示:本文适用于所有Java Maven项目(单模块、多模块,SpringBoot/SpringCloud项目均适用),重点解决“依赖优先级怎么判断、多模块中依赖怎么继承、传递依赖为什么失效、BOM依赖怎么生效、冲突怎么解决”五大核心痛点,建议收藏,遇到依赖问题直接对照查询。

一、先明确核心前提:Maven依赖优先级的核心原则

在讲解具体优先级规则前,先记住3个核心原则,这是理解所有优先级模式的基础,避免走偏:

  1. 就近优先:依赖的引入路径越短,优先级越高(比如直接引入的依赖,比通过其他依赖传递引入的优先级高);

  2. 声明优先:在同一引入路径下,依赖在pom.xml中声明的位置越靠前,优先级越高;

  3. 覆盖优先:子模块直接声明的依赖 > 父模块声明的依赖;直接引入的依赖 > 传递依赖;BOM管理的依赖版本,可被直接声明的版本覆盖。

补充:Maven依赖优先级的本质是“解决依赖冲突”——当同一依赖出现多个版本时,Maven会根据优先级规则,选择一个“最优版本”引入项目,避免重复引入导致的类加载异常、方法找不到等问题。另外,Spring Boot项目中常用的BOM(Bill of Materials)依赖管理,本质是“统一版本优先级”,并非打破Maven核心优先级规则。

二、单模块Maven依赖优先级(基础模式,必吃透)

单模块项目(无父模块/子模块)的依赖优先级,主要分为「直接依赖vs传递依赖」「依赖范围优先级」「声明顺序优先级」3种模式,是多模块、BOM依赖优先级的基础,逐一拆解:

2.1 模式1:直接依赖 > 传递依赖(核心优先级)

这是最基础、最常用的优先级规则:项目中直接在pom.xml中引入的依赖(直接依赖),优先级高于通过其他依赖传递引入的依赖(传递依赖)

实操案例(避坑重点):

<!-- 直接依赖:项目中直接引入fastjson2,版本2.0.32 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.32</version>
</dependency>

<!-- 传递依赖:引入xxx-service,该依赖传递引入fastjson2,版本2.0.20 -->
<dependency>
    <groupId>com.xxx</groupId>
    <artifactId>xxx-service</artifactId>
    <version>1.0.0</version>
</dependency>

结论:此时项目中生效的fastjson2版本是2.0.32(直接依赖优先级高于传递依赖),即使传递依赖的版本更低/更高,也会被直接依赖覆盖。

避坑点:很多开发者误以为“传递依赖的版本更高,就会生效”,实则不然——无论传递依赖版本如何,只要有直接依赖,就优先使用直接依赖的版本。

2.2 模式2:同一层级,声明顺序优先

当两个「直接依赖」是同一层级(无父子依赖关系),且引入的是同一个依赖的不同版本时,在pom.xml中声明位置越靠前的依赖,优先级越高

实操案例:

<!-- 声明在前面的依赖,版本2.0.32 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.32</version>
</dependency>

<!-- 声明在后面的依赖,版本2.0.40 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.40</version>
</dependency>

结论:此时项目中生效的fastjson2版本是2.0.32(声明在前的优先级更高),而非版本更高的2.0.40。

避坑点:不要误以为“版本越高,优先级越高”——Maven的依赖优先级和版本高低无关,同一层级下,只看声明顺序。

2.3 模式3:依赖范围(scope)的优先级

依赖范围(scope)不仅决定了依赖的生效阶段,也间接影响依赖优先级,核心优先级顺序(从高到低),结合生效场景拆解,避免记混:

compile > provided > runtime > test

逐一代码+场景解析,结合实操案例,一看就懂:

3.1 compile(默认范围,优先级最高)

生效阶段:编译、测试、运行,全程生效,是项目核心依赖的首选范围(如Spring核心、MyBatis、工具类等)。

优先级最高,无论其他范围的同依赖版本如何,compile范围的依赖都会优先生效。

3.2 provided(次高优先级)

生效阶段:编译、测试,运行时由容器提供(如Tomcat提供servlet-api),不参与项目打包。

实操案例:

<!-- provided范围:servlet-api,运行时由Tomcat提供 -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>6.0.0</version>
    <scope>provided</scope>
</dependency>

<!-- test范围:同依赖,版本5.0.0 -->
<dependency>
    <groupId>jakarta.servlet</groupId>
    <artifactId>jakarta.servlet-api</artifactId>
    <version>5.0.0</version>
    <scope>test</scope>
</dependency>

结论:编译、测试阶段,生效的是provided范围的6.0.0版本(优先级高于test);运行时不加载该依赖,由容器提供。

3.3 runtime(次低优先级)

生效阶段:测试、运行,编译时不生效(无需参与编译,仅运行时需要),典型场景:数据库驱动(MySQL、Oracle驱动)。

优先级低于compile、provided,高于test——若存在compile范围的同依赖,会被compile覆盖。

3.4 test(最低优先级)

生效阶段:仅测试阶段(如JUnit、Mockito),不参与编译和运行,优先级最低。

避坑点:test范围的依赖,无法在主代码中使用(编译时不生效);若其他范围有同依赖,test范围的版本会被覆盖。

2.4 补充:system范围的特殊说明

除了上述4个常用范围,还有一个system范围(本地依赖,需指定systemPath),其优先级与provided一致,但不推荐使用(依赖本地文件,移植性差,容易导致团队协作问题)。

三、多模块Maven依赖优先级(重点,实战高频)

多模块项目(父模块+子模块)的依赖优先级,是实战中最容易踩坑的场景——核心是“继承+覆盖”,结合Maven核心原则,拆解3种核心模式,覆盖所有多模块场景(父模块聚合、子模块嵌套、跨模块依赖)。

先明确多模块基础架构(后文案例基于此架构):

xxx-project(父模块,pom打包,仅管理依赖和聚合子模块)

├─ xxx-common(子模块,公共工具模块)

├─ xxx-service(子模块,业务逻辑模块,依赖common)

└─ xxx-web(子模块,展示层模块,依赖service、common)

3.1 模式1:子模块直接声明 > 父模块声明(覆盖优先)

核心规则:子模块会继承父模块的依赖(父模块dependencyManagement或dependencies中声明的依赖),但子模块直接在pom.xml中声明的依赖,优先级高于父模块声明的依赖(子模块可覆盖父模块的依赖版本)。

实操案例(父模块+子模块):

父模块pom.xml(依赖管理):
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xxx</groupId>
    <artifactId>xxx-project</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>xxx-common</module>
        <module>xxx-service</module>
        <module>xxx-web</module>
    </modules>

    <!-- 父模块依赖管理:统一版本,子模块可直接引用 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson2</artifactId>
                <version>2.0.32</version> <!-- 父模块统一版本 -->
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>
子模块(xxx-service)pom.xml:
<project>
    <parent>
        <groupId>com.xxx</groupId>
        <artifactId>xxx-project</artifactId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>xxx-service</artifactId>

    <!-- 子模块直接声明fastjson2,版本2.0.40 -->
    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.40</version><!-- 覆盖父模块版本 -->
        </dependency>
    </dependencies>
</project>

结论:xxx-service模块中,生效的fastjson2版本是2.0.40(子模块直接声明 > 父模块声明);其他未直接声明的子模块(如xxx-common),若引用fastjson2,会使用父模块的2.0.32版本。

避坑点:父模块的dependencyManagement标签,仅“管理版本”,不自动引入依赖;子模块需手动引入依赖(无需写version),若子模块写了version,会覆盖父模块的版本。

3.2 模式2:跨模块依赖,就近优先(路径越短,优先级越高)

多模块中,子模块之间会相互依赖(如web依赖service,service依赖common),此时依赖优先级遵循“就近优先”——直接依赖的子模块,比间接依赖的子模块中的传递依赖优先级高

实操案例:

  1. xxx-common模块(子模块):引入fastjson2,版本2.0.20(直接依赖);

  2. xxx-service模块(子模块):依赖xxx-common(传递引入fastjson2 2.0.20),同时直接引入fastjson2 2.0.32;

  3. xxx-web模块(子模块):依赖xxx-service(传递引入fastjson2 2.0.32),未直接声明。

依赖路径分析:

  • xxx-web → xxx-service → fastjson2 2.0.32(路径长度2);

  • xxx-web → xxx-service → xxx-common → fastjson2 2.0.20(路径长度3)。

结论:xxx-web模块中,生效的fastjson2版本是2.0.32(路径更短,优先级更高)。

3.3 模式3:子模块嵌套(子子模块),继承+覆盖优先级

若项目存在子子模块(如xxx-service下有service-user、service-order子模块),优先级规则不变,核心:子子模块直接声明 > 父模块(xxx-service)声明 > 根父模块(xxx-project)声明

避坑点:子子模块的依赖,优先继承直接父模块(如service-user继承xxx-service),再继承根父模块;若子子模块直接声明依赖,会覆盖所有父模块的版本。

四、Spring Boot BOM依赖管理优先级(实战重点,必懂)

Java开发者几乎都用Spring Boot,而Spring Boot的核心依赖管理方式是「BOM(Bill of Materials)」——通过spring-boot-starter-parent或spring-boot-dependencies,统一管理所有Spring Boot相关依赖的版本,其优先级有特殊规则,也是高频踩坑点。

先明确:Spring Boot BOM的核心作用是“统一依赖版本,简化依赖引入”,它不打破Maven核心优先级规则,而是在“依赖版本管理”层面提升优先级。

4.1 BOM依赖的两种引入方式(优先级不同)

Spring Boot项目引入BOM,有两种常用方式,优先级有差异,重点区分:

方式1:继承spring-boot-starter-parent(推荐,优先级高)

spring-boot-starter-parent本身就是一个BOM,继承它后,子模块会自动继承所有Spring Boot官方依赖的版本,优先级:父模块(spring-boot-starter-parent)依赖版本 > 手动引入的BOM版本

<!-- 继承Spring Boot BOM(spring-boot-starter-parent) -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.9</version> <!-- BOM统一版本 -->
</parent>

<dependencies>
    <!-- 无需写version,自动继承BOM中的版本(3.2.9对应的spring-web版本) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
方式2:在dependencyManagement中引入spring-boot-dependencies(灵活,优先级低)

若项目无法继承spring-boot-starter-parent(如已有自定义父模块),可在dependencyManagement中引入BOM,优先级:自定义父模块依赖版本 > BOM版本

<!-- 自定义父模块 -->
<parent>
    <groupId>com.xxx</groupId>
    <artifactId>xxx-project</artifactId>
    <version>1.0.0</version>
</parent>

<dependencyManagement>
    <dependencies>
        <!-- 引入Spring Boot BOM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- 自定义父模块声明的依赖,优先级高于BOM -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.2.8</version><!-- 覆盖BOM的3.2.9版本 -->
        </dependency>
    </dependencies>
</dependencyManagement>

4.2 BOM依赖的核心优先级规则(实战必记)

结合Maven核心原则,Spring Boot BOM依赖的优先级顺序(从高到低):

  1. 项目直接声明的依赖(带version):无论BOM中版本如何,直接声明的依赖版本优先生效(覆盖BOM版本);

  2. 继承spring-boot-starter-parent的依赖版本:若未直接声明,优先使用父模块(BOM)中的版本;

  3. dependencyManagement中引入的BOM版本:若未继承父模块BOM,且未直接声明,使用该BOM版本;

  4. 传递依赖版本:若以上都未配置,使用传递依赖的版本。

4.3 BOM依赖避坑点(高频问题)

  • 坑点1:“引入BOM后,依赖版本仍不生效”——原因是BOM仅管理版本,需手动引入依赖(无需写version),不引入则不生效;

  • 坑点2:“直接声明依赖版本,却被BOM覆盖”——不可能!直接声明的依赖(带version)优先级高于BOM,若不生效,检查是否有拼写错误(groupId/artifactId错误);

  • 坑点3:“多模块中,部分子模块BOM版本不统一”——需确保所有子模块都继承同一个BOM(如统一继承spring-boot-starter-parent),避免子模块单独引入不同版本的BOM。

五、传递依赖的特殊优先级(避坑重点)

传递依赖是依赖冲突的主要来源,除了“直接依赖 > 传递依赖”“就近优先”,还有3个特殊优先级规则,实战中经常遇到,逐一拆解:

5.1 传递依赖的“路径长度优先”

当同一个依赖通过多个不同路径传递引入时,路径长度越短,优先级越高(路径长度=依赖层级数)。

案例:web模块依赖A和B两个依赖,A传递引入fastjson2 2.0.32(路径:web→A→fastjson2,长度2),B传递引入fastjson2 2.0.40(路径:web→B→C→fastjson2,长度3),此时生效版本是2.0.32(路径更短)。

5.2 传递依赖的“声明顺序优先”

当同一个依赖的传递路径长度相同时,传递依赖的“源头依赖”在pom.xml中声明越靠前,优先级越高

案例:web模块同时依赖A和B,A传递引入fastjson2 2.0.32,B传递引入fastjson2 2.0.40,路径长度都是2(web→A→fastjson2、web→B→fastjson2),若A在pom.xml中声明在B前面,生效版本是2.0.32。

5.3 排除传递依赖(打破优先级)

若传递依赖的版本不兼容,可通过<exclusions>标签排除传递依赖,此时排除后,该传递依赖失效,优先级最低(相当于被“删除”)。

<!-- 引入xxx-service,排除其传递引入的fastjson2 -->
<dependency>
    <groupId>com.xxx</groupId>
    <artifactId>xxx-service</artifactId>
    <version>1.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson2</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 直接引入fastjson2,版本2.0.32 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.32</version>
</dependency>

结论:排除xxx-service传递的fastjson2后,仅生效直接引入的2.0.32版本。

六、依赖优先级实战避坑+冲突解决技巧

结合前面的所有规则,整理10个实战高频避坑点+冲突解决技巧,遇到依赖问题直接对照,高效解决:

6.1 高频避坑点(必记)

  1. 优先级和版本高低无关:不要误以为“版本越高,优先级越高”,Maven只看“路径、声明顺序、是否直接引入”;

  2. 子模块覆盖父模块:子模块直接声明的依赖,无论版本高低,都覆盖父模块的依赖;

  3. BOM不自动引入依赖:引入BOM后,需手动引入依赖(无需写version),否则依赖不生效;

  4. 传递依赖路径越短越好:尽量减少传递依赖的层级,避免路径过长导致的冲突;

  5. 多模块统一BOM:所有子模块尽量继承同一个BOM,避免版本混乱。

6.2 依赖冲突解决技巧(实战必备)

  1. 排查冲突:执行mvn dependency:tree -Dverbose,查看依赖树,找到冲突的依赖(标注“omitted for conflict with xxx”的就是被排除的版本);

  2. 解决冲突(优先方案):直接引入需要的依赖版本(直接依赖覆盖传递依赖);

  3. 解决冲突(备选方案):排除不兼容的传递依赖(通过<exclusions>标签);

  4. 多模块冲突:在父模块的dependencyManagement中统一依赖版本,所有子模块引用时不写version;

  5. BOM冲突:直接声明需要的依赖版本,覆盖BOM中的版本。

七、总结(实战重点回顾)

Maven依赖优先级的核心,始终围绕“就近优先、声明优先、覆盖优先”三大原则,无论是单模块、多模块,还是Spring Boot BOM依赖,都没有脱离这三大原则——所有特殊场景(如BOM、传递依赖),都是在这三大原则的基础上延伸的。

核心优先级总排序(实战直接对照):

项目直接声明的依赖(带version) > 父模块/BOM声明的依赖 > 传递依赖(路径短的优先) > 传递依赖(路径长的,声明在前的优先)

补充:依赖范围的优先级(compile > provided > runtime > test),仅在“同一引入方式、同一路径”下生效。

对于Java程序员来说,吃透Maven依赖优先级,能快速解决90%的依赖冲突问题,提升开发效率,避免因依赖问题导致的项目启动失败、功能异常。本文覆盖了所有高频场景(单模块、多模块、BOM、传递依赖),搭配实操案例和避坑技巧,建议收藏,遇到依赖问题直接对照查询。

如果觉得有用,欢迎点赞+关注,持续分享Java Maven、Spring Boot实战干货;若有具体的依赖冲突场景(如多模块+BOM冲突),可在评论区留言,我帮你拆解解决方案!

资料获取,更多粉丝福利,关注下方公众号获取

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐