dependencies
本章是"依赖机制"篇章的起点。理解
dependencies标签的作用,是掌握 Maven 声明式依赖管理的基础,也是告别"手动下载 JAR"时代的分水岭。
核心机制
Maven 通过项目对象模型(POM)中的 dependencies 部分,提供了一种声明式的依赖管理机制。开发者只需声明"项目需要什么",Maven 负责解决"从哪里获取、如何传递、版本是否冲突"等所有细节。
这句话的关键词是声明式(Declarative)。你不再是一个搬运工(手动下载 JAR),而是一个指挥官(在 pom.xml 里写下坐标,Maven 自动调度)。
什么是声明式依赖管理?
在 Maven 之前,Java 项目的依赖管理是"命令式"的——你亲自执行每一步:
- 打开浏览器,访问 Spring 官网
- 找到
spring-context-5.3.21.jar,点击下载 - 复制到项目的
lib/目录 - 如果
spring-context依赖了spring-core、spring-beans,重复步骤 1-3 - 手动编写 classpath:
javac -cp lib/spring-context.jar:lib/spring-core.jar:lib/spring-beans.jar ...
Maven 将这一切压缩为一句声明:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
dependencies 在 POM 中的位置
dependencies 是 pom.xml 中 <project> 的直接子元素,位于 <dependencies> 标签内:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 项目坐标 -->
<groupId>com.feixiang</groupId>
<artifactId>employee-system</artifactId>
<version>1.0.0</version>
<!-- 依赖声明区 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
每个 <dependency> 内部的核心子元素:
| 元素 | 含义 | 是否必填 |
|---|---|---|
groupId | 组织/公司域名倒写 | 是 |
artifactId | 项目/模块名称 | 是 |
version | 版本号 | 是(子模块可从父 POM 继承) |
scope | 依赖范围(compile/test/provided/runtime/system) | 否,默认 compile |
optional | 是否可选依赖 | 否,默认 false |
exclusions | 排除特定传递依赖 | 否 |
type | 依赖类型(jar/pom/war 等) | 否,默认 jar |
classifier | 分类器(如 sources、javadoc) | 否 |
依赖声明前后对比
| 维度 | 手动管理(无 Maven) | 声明式管理(Maven) |
|---|---|---|
| 获取依赖 | 手动下载,复制到 lib/ | 声明坐标,mvn 自动从仓库下载 |
| 传递依赖 | 手动追踪,漏一个就编译失败 | 自动解析传递依赖 |
| 版本控制 | 文件名里写版本号,容易混乱 | 坐标里精确声明版本 |
| 团队协作 | 新成员需要手动配齐所有 JAR | 执行 mvn compile 自动补齐 |
| classpath | 手动拼写,容易遗漏 | 自动计算,零配置 |
| 升级依赖 | 删除旧 JAR,下载新 JAR,改 classpath | 改 version 数字,执行 mvn |
生活类比:从"自己抓药"到"医院处方"
想象你生病了需要吃药:
- 手动管理(无 Maven):你自己去药店,根据症状挑选药品。感冒需要感冒药、退烧药、维生素 C,每种药你都要亲自找、亲自核对生产日期。如果感冒药里已经包含了退烧成分,你可能重复购买,吃错剂量。
- 声明式管理(Maven):你去医院,医生开一张处方单(
pom.xml),上面写着"感冒药一盒"。药房(Maven 仓库)自动配齐所有成分——主药、辅料、说明书,并且检查药物之间是否冲突(依赖调解)。你只需把处方交给药房,拿到的是一份安全、完整的药包。
图示
上图展示了 dependencies 的核心价值:开发者从"执行者"降级为"声明者",Maven 从"工具"升级为"管家"。你只需在处方单上写药名,药房负责抓药、配药、核对禁忌。
完整示例
场景
飞翔科技的员工管理系统 employee-system 需要引入 Spring 框架和 MySQL 驱动。CTO 大翔要求所有依赖必须通过 Maven 管理,禁止手动下载 JAR。后端小崔负责引入依赖,运维李眉负责验证构建环境的一致性。
操作前:手动管理的噩梦
小崔回忆上一个非 Maven 项目的经历:
employee-system-legacy/
├── src/
│ └── com/feixiang/...
├── lib/ # 手动维护的 JAR 仓库
│ ├── spring-context-5.3.21.jar # 小崔从 Spring 官网下载
│ ├── spring-core-5.3.21.jar # 漏传这个,编译报错
│ ├── spring-beans-5.3.21.jar # 漏传这个,运行时报 NoClassDefFoundError
│ ├── spring-aop-5.3.21.jar # 白歌提醒后才补上
│ ├── mybatis-3.5.9.jar
│ ├── mysql-connector-java-8.0.30.jar
│ └── junit-5.8.2.jar # 版本写成了 5.8.1,测试跑不过
├── build.sh # 手动拼 classpath 的脚本
└── README.md # "请确保 lib/ 目录完整"
build.sh 片段:
CLASSPATH="lib/spring-context-5.3.21.jar:lib/spring-core-5.3.21.jar:lib/spring-beans-5.3.21.jar:lib/spring-aop-5.3.21.jar:lib/mybatis-3.5.9.jar:lib/mysql-connector-java-8.0.30.jar"
javac -cp "$CLASSPATH" src/com/feixiang/*.java
问题:
- 小崔的 Mac 上 classpath 用
:分隔,黄俪的 Windows 上要用;,脚本不通用 - 李眉部署到 Linux 服务器时,发现
lib/里少了spring-expression.jar,启动失败 - 升级 Spring 到 5.3.22 时,小崔要手动替换 4 个 JAR 文件,漏了一个导致版本不一致
操作后:声明式依赖管理
小崔创建 Maven 项目,在 pom.xml 中声明依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.feixiang</groupId>
<artifactId>employee-system</artifactId>
<version>1.0.0</version>
<dependencies>
<!-- Spring Context:自动传递 spring-core、spring-beans 等 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.21</version>
</dependency>
<!-- MyBatis:持久层框架 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- JUnit:仅测试使用 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
执行命令:
mvn compile
输出:
[INFO] Scanning for projects...
[INFO] --- maven-dependency-plugin:2.8:tree ---
[INFO] com.feixiang:employee-system:jar:1.0.0
[INFO] +- org.springframework:spring-context:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-aop:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-beans:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-core:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-expression:jar:5.3.21:compile
[INFO] +- org.mybatis:mybatis:jar:3.5.9:compile
[INFO] +- mysql:mysql-connector-java:jar:8.0.30:compile
[INFO] \- org.junit.jupiter:junit-jupiter:jar:5.8.2:test
[INFO] \- ...
[INFO] BUILD SUCCESS
变化分析:
- 小崔只声明了 4 个依赖,Maven 自动解析出 10+ 个传递依赖(
spring-aop、spring-beans、spring-core等) - 黄俪在 Windows 上执行同样的
mvn compile,下载的 JAR 和小崔的 Mac 完全一致 - 李眉部署时,只需执行
mvn package,无需关心 classpath 怎么拼 - 升级 Spring 时,小崔只需改
version标签里的数字,Maven 自动替换整个依赖树
易错点与常见问题
误区一:dependencies 里写得越多越好
错误认知:"我把可能用到的库都写进 dependencies,省得以后再加。"
纠正:每个 <dependency> 都会进入依赖树,增加构建时间、打包体积和潜在冲突。只声明项目直接需要的依赖。例如:你的代码里直接 import org.springframework.context.ApplicationContext,才需要声明 spring-context;spring-core 是传递依赖,不需要手动声明。
误区二:version 可以随便写,Maven 会找最近的
错误认知:"我写 version>1.0</version>,Maven 会自动找最新版。"
纠正:Maven 对版本号是精确匹配的。写 1.0 就找 1.0,不会自动升级成 1.1。如果你想使用版本范围,需要显式写 [1.0,2.0) 或 LATEST(不推荐用于生产)。生产项目应该精确锁定版本号,确保构建可复现。
误区三:dependencies 和 dependencyManagement 是一回事
错误认知:"我在父 POM 的 dependencyManagement 里写了依赖,子模块就能用了。"
纠正:dependencyManagement 只声明版本,不引入依赖。子模块需要在 dependencies 里再次声明(可以省略 version),依赖才真正进入 classpath。这是本章与后续 dependencyManagement 章节的关键区别,后续会详细展开。
小结
dependencies 标签是 Maven 声明式依赖管理的入口。开发者只需用 GAV 坐标(groupId + artifactId + version)声明项目直接需要的库,Maven 自动完成下载、传递、classpath 计算等所有细节。与手动下载 JAR 相比,声明式管理带来了版本精确控制、团队协作零摩擦、构建环境可复现的三重收益。
本章与全局的关系:本章回答了"如何声明依赖"。下一章"依赖传递"将深入讲解"你声明了 A,Maven 为什么自动引入了 B 和 C"——这是 Maven 依赖管理最精妙的设计之一。