Maven 源码阅读 —— 依赖解析机制深度分析
依赖解析是 Maven 最复杂的部分,涉及"声明 → 传递展开 → 冲突仲裁 → 下载 → 构建 classpath"多个环节。本文从源码分层模型出发,拆解 DependencyResolver 与 ArtifactResolver 的职责边界。
依赖解析的分层架构
Maven 的依赖解析由三层协作完成:
┌─────────────────────────────────────────────┐
│ DependencyResolver(高层:依赖图与仲裁) │
│ org.apache.maven.project.artifact │
├─────────────────────────────────────────────┤
│ ArtifactResolver(中层:构件定位与下载) │
│ org.apache.maven.artifact.resolver │
├─────────────────────────────────────────────┤
│ RepositorySystem(底层:仓库访问) │
│ org.eclipse.aether Maven Resolver │
└─────────────────────────────────────────────┘| 层 | 职责 | 关键类 |
|---|---|---|
| DependencyResolver | 依赖图构建、冲突仲裁、scope 传递 | DefaultDependencyResolver |
| ArtifactResolver | 把"坐标"解析为文件,处理下载/缓存 | DefaultArtifactResolver |
| RepositorySystem | 仓库元数据、传输、远程/本地仓库操作 | DefaultRepositorySystem |
顶层在 3.x 中逐步向 Aether(Maven Resolver)演进,底层已经全面基于 maven-resolver 实现。
DependencyResolver:依赖图构建
DefaultDependencyResolver 负责把项目的依赖声明展开为完整依赖图:
项目 POM 的 dependencies
│
▼
解析每个直接依赖的传递依赖
│
▼
递归构建 DependencyGraph
│
▼
冲突仲裁(版本选择)
│
▼
输出:已仲裁的依赖集合(含 scope)关键类与方法
java
// org.apache.maven.project.artifact.DefaultDependencyResolver
public class DefaultDependencyResolver implements DependencyResolver {
public ResolutionResult resolve(DependencyResolutionRequest request) {
// 1. 收集依赖声明(含 dependencyManagement 约束)
// 2. 委托 RepositorySystem 构建依赖图(collectDependencies)
// 3. 对图做冲突仲裁(选择器)
// 4. 转换/过滤:scope 处理、optional、exclusions
}
}传递依赖展开
- 直接依赖的 POM 被读取(
readArtifactMetadata),取出其<dependencies> - 递归展开直至叶子
- 每层应用 scope 传递规则(compile 传递、test 不传递等)
冲突仲裁的实现
仲裁在 Aether 层的版本选择器中完成,但仲裁规则体现在依赖图中:
Aether 依赖图(DependencyNode 树)
│
▼
DefaultDependencySelector + NearestVersionSelector
├─ 最短路径优先:比较路径深度
└─ 最先声明优先:深度相同时按声明顺序仲裁后每个冲突版本只保留一个节点参与后续解析。
ArtifactResolver:构件定位
DefaultArtifactResolver 把抽象坐标变成具体文件路径:
Artifact(坐标)
│ groupId:artifactId:version:packaging
▼
定位仓库路径:本地仓库 → 远程仓库
│
▼
远程未命中 → 下载 → 缓存到本地仓库
│
▼
返回文件路径java
// org.apache.maven.artifact.resolver.DefaultArtifactResolver
public class DefaultArtifactResolver implements ArtifactResolver {
public Artifact resolve(Artifact artifact, ...) {
// 1. 计算构件在本地仓库的预期路径
// 2. 检查本地仓库是否存在
// 3. 不存在 → 委托 RepositorySystem 从远程解析下载
// 4. 校验(校验和、签名可选)
}
}解析顺序与元数据
解析一个构件可能需要的文件:
1. 主构件 xxx-1.0.0.jar
2. POM xxx-1.0.0.pom(传递依赖来源)
3. 元数据 maven-metadata.xml(SNAPSHOT 版本解析、版本列表)SNAPSHOT 版本通过 maven-metadata.xml 定位实际时间戳版本,因此每次构建可能拉取不同的快照。
仓库访问:RepositorySystem
底层统一走 Aether(Maven Resolver):
RepositorySystem
├─ collectDependencies:构建依赖图(含仲裁)
├─ resolveArtifact:解析单个构件
├─ resolveArtifacts:批量解析
└─ 支持代理、镜像、认证、离线模式镜像与代理的处理
Aether 的 RepositorySystemSession 携带:
MirrorSelector:按 mirrorOf 匹配镜像仓库ProxySelector:选择代理RepositoryListener:记录下载事件(构建日志"Downloading/Downloaded"的来源)LocalRepositoryManager:本地仓库读写
依赖解析的完整时序
mvn compile
│
▼
1. ProjectBuilder 构建项目模型(读取 POM)
│
2. DefaultDependencyResolver.resolve(编译 classpath)
│
├─ collectDependencies(Aether):构建依赖图
│ └─ 递归读取依赖 POM,展开传递依赖
│ └─ 版本仲裁(最短路径 > 最先声明)
│
├─ filterDependencies:按 scope/optional/exclusions 过滤
│
└─ 得到解析结果列表
│
3. compiler 插件需要 classpath
│
▼
4. ArtifactResolver.resolve(下载缺失构件到本地仓库)
│
5. 生成 classpath 交给编译器关键源码文件索引
| 类 | 路径 |
|---|---|
| DefaultDependencyResolver | maven-core/src/main/java/org/apache/maven/project/artifact/DefaultDependencyResolver.java |
| DefaultArtifactResolver | maven-core/src/main/java/org/apache/maven/artifact/resolver/DefaultArtifactResolver.java |
| DefaultProjectDependenciesResolver | maven-core/.../internal/aether/DefaultProjectDependenciesResolver.java |
| RepositorySystem | maven-resolver-api/.../RepositorySystem.java |
| DefaultRepositorySystem | maven-resolver-impl/.../DefaultRepositorySystem.java |
常见问题
- 为什么依赖解析很慢? 每次构建会检查 SNAPSHOT 的远程元数据;本地仓库有缓存时跳过下载,但 SNAPSHOT 默认要查最新版本(可配
-o离线或修改 updatePolicy)。 - 依赖下载失败但本地有 jar? 本地仓库缓存机制基于坐标,若元数据损坏或校验失败会重新下载;
mvn -U强制刷新快照。 - 如何看解析过程?
mvn dependency:tree看结果;mvn compile -X看详细解析日志(Debug 模式)。