一文彻底搞懂 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 游戏一样,你给每个依赖分配"职业技能",

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

相关推荐
tg-zm8899964 小时前
2025返利商城源码/挂机自动收益可二开多语言/自定义返利比例/三级分销理财商城
java·mysql·php·laravel·1024程序员节
X***C8624 小时前
SpringBoot:几种常用的接口日期格式化方法
java·spring boot·后端
前端达人4 小时前
你的App消息推送为什么石沉大海?看Service Worker源码我终于懂了
java·开发语言
小光学长4 小时前
基于ssm的宠物交易系统的设计与实现850mb48h(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
编程大师哥4 小时前
vxe-table 透视表分组汇总及排序基础配置
java
8***84825 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
9***J6285 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
M***Z2105 小时前
SQL 建表语句详解
java·数据库·sql
v***7945 小时前
Spring Boot 热部署
java·spring boot·后端
执笔论英雄5 小时前
【RL】python协程
java·网络·人工智能·python·设计模式