一文彻底搞懂 Maven 依赖——从 <dependency> 到依赖冲突,带你看懂 Maven 的“江湖规矩”

🌱 一、前言:为什么要研究依赖?

写 Java 项目,谁没被 Maven "支配"过呢?

你加了个 Spring Boot Starter,结果一堆库跟着进来;

别人告诉你"scope 写错了";

编译正常但运行报错,或者 jar 包体积暴涨到 200MB。

这一切背后,其实都是 Maven 依赖系统 在发挥作用。

要真正掌握 Maven,就得先搞清楚:

"依赖是什么?"、"它怎么传递?"、"怎么解决冲突?"、"什么时候该 provided?"


🧱 二、依赖的本质:三段坐标

Maven 的核心设计哲学之一是"声明式依赖"。

你不需要手动下载 jar,只要写出三个坐标:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.2</version>
</dependency>

这三个坐标就像一个图书馆的"索书号":

  • groupId:组织名(相当于出版社)
  • artifactId:模块名(相当于书名)
  • version:版本号(相当于第几版)

Maven 仓库 = 全球最大"开源图书馆"

依赖声明 = 你要借的"书目清单"


🧩 三、Maven 的依赖来源

Maven 在解析依赖时,会按照以下顺序查找 jar 包:

  1. 本地仓库~/.m2/repository
    → 最近一次构建下载过的包会被缓存到这里。
  2. 远程中央仓库https://repo.maven.apache.org/maven2/
    → Maven 官方中央仓库。
  3. 私有仓库 (公司 Nexus / Artifactory)
    → 企业内部维护的依赖镜像。

Maven 会自动从上往下找,找不到就报错:

"Could not resolve dependencies..."


🧠 四、依赖范围(Scope)详解

Scope 是 Maven 的依赖生命周期规则,定义了依赖在哪些阶段可用、是否参与打包、是否传递。

Scope 编译时可见 测试时可见 运行时可见 打包带上 可传递 典型场景
compile 默认值,大多数库
provided 容器已提供(Servlet、Lombok)
runtime JDBC Driver、Logback
test JUnit、Mockito
system 手动指定 jar
import --- --- --- --- --- 仅用于依赖管理

🧩 五、每种 Scope 的典型示例

1️⃣ compile ------ 默认的依赖方式

xml 复制代码
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

特点:

  • 编译、运行、测试全阶段可用;
  • 可传递;
  • 打包会带上。

适合:核心依赖(比如 Spring Context、Apache Commons)。


2️⃣ provided ------ 编译要用,运行别带

xml 复制代码
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

适合:由容器(Tomcat、Jetty)或环境提供的类库。

打包带上会冲突。


3️⃣ runtime ------ 运行时才需要的依赖

xml 复制代码
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.1.0</version>
    <scope>runtime</scope>
</dependency>

特点:

  • 编译不需要(用接口即可);
  • 运行时才加载;
  • 打包会带上。

适合:数据库驱动、日志实现等。


4️⃣ test ------ 仅在测试阶段使用

xml 复制代码
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>

不会参与最终打包,测试用完即止。


5️⃣ system ------ 手动指定路径

xml 复制代码
<dependency>
    <groupId>com.company</groupId>
    <artifactId>internal-lib</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/internal-lib.jar</systemPath>
</dependency>

⚠️ 注意:

  • 不推荐使用;
  • 不可传递;
  • 会破坏构建的可移植性。

6️⃣ import ------ 依赖版本管理用

用于在 dependencyManagement 中引入 BOM(Bill of Materials)

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>3.3.2</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

它不会引入依赖本身,只是导入一组"版本约定"。


🧩 六、依赖传递机制:Maven 的"层层借书"

假设:

  • A → 依赖 B
  • B → 依赖 C

则 A 间接依赖了 C(称为传递依赖)。

Maven 的传递规则如下:

A 的 Scope B 的 Scope C 是否传递 说明
compile compile 默认传递
compile provided 不传递
provided compile 不传递
test 任意 不传递
runtime compile/runtime 传递

简单理解:

只有"compile"或"runtime"的依赖才会往下传递。

test / provided 不会传递。


⚔️ 七、依赖冲突与解决策略

当两个不同版本的相同依赖出现时:

  • 最近路径优先(Nearest Definition Wins)
    → Maven 会选择依赖树中路径最短的版本。

例:

less 复制代码
A → B → commons-lang3:3.12.0  
A → C → commons-lang3:3.14.0

A 直接依赖 C 的路径更短,则取 3.14.0。

如果两者路径一样长:

  • 则选择 声明顺序靠前 的依赖。

💡 查看依赖树命令:

复制代码
mvn dependency:tree

可查看传递依赖及冲突来源。


🔧 强制指定版本:

xml 复制代码
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.14.0</version>
    </dependency>
  </dependencies>
</dependencyManagement>

dependencyManagement 只定义版本,不自动引入依赖。


🧩 八、依赖排除(Exclusion)

有时候我们不想要某个传递依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

比如:自己要用 Undertow 或 Jetty,而不想要 Tomcat。


🧩 九、最佳实践总结

场景 Scope 建议 原因
普通库依赖 compile 默认
容器内置库(Servlet、JSP) provided 环境已提供
运行时驱动(JDBC、日志实现) runtime 只运行时用
测试框架 test 不参与打包
编译工具(Lombok、MapStruct) provided 编译期生效
公司内部 jar system(慎用) 构建可移植性差
统一管理版本 import(BOM) 方便升级维护

🌟 十、一句话总结

依赖管理,是项目整洁性的基石。

依赖范围(Scope)决定了依赖的"生死周期";

依赖传递规则决定了"家族关系";

依赖冲突解决机制,则是 Maven 的"江湖规矩"。

🥳 彩蛋时间

💡 记忆口诀:

compile → 全能型
provided → 借用不打包
runtime → 运行才用
test → 只在测试用

像玩 RPG 游戏一样,你给每个依赖分配"职业技能",

打包、传递、运行都明明白白,不再踩坑!

相关推荐
一瓢一瓢的饮 alanchan2 小时前
Flink原理与实战(java版)#第1章 Flink快速入门(第一节IDE词频统计)
java·大数据·flink·kafka·实时计算·离线计算·流批一体化计算
java_logo2 小时前
Docker 容器化部署 QINGLONG 面板指南
java·运维·docker·容器·eureka·centos·rabbitmq
那我掉的头发算什么2 小时前
【javaEE】多线程--认识线程、多线程
java·jvm·redis·性能优化·java-ee·intellij-idea
Pluchon2 小时前
硅基计划6.0 JavaEE 叁 文件IO
java·学习·java-ee·文件操作·io流
程序员卷卷狗2 小时前
联合索引的最左前缀原则与失效场景
java·开发语言·数据库·mysql
纪莫2 小时前
技术面:SpringCloud(SpringCloud有哪些组件,SpringCloud与Dubbo的区别)
java·spring·java面试⑧股
会编程的吕洞宾3 小时前
Java中的“万物皆对象”:一场编程界的哲学革命
java·后端
会编程的吕洞宾3 小时前
Java封装:修仙界的"护体罡气"
java·后端
豆沙沙包?3 小时前
2025年--Lc231-350. 两个数组的交集 II-Java版
java·开发语言