Maven 源码阅读 —— Aether 依赖解析器源码深度分析
Maven 3 的依赖解析最终都落在 Maven Resolver(原 Eclipse Aether)上。Aether 是独立于 Maven 的通用仓库访问与依赖解析库,被 Gradle、Maven、sbt 等构建工具采用。理解 Aether,才算真正理解依赖解析。
Aether / Maven Resolver 简介
演进
| 阶段 | 说明 |
|---|---|
| Eclipse Aether | Sonatype 在 Maven 3 时代提出的独立依赖解析库 |
| Maven Resolver | Aether 并入 Apache Maven 项目后的官方名(maven-resolver) |
| 现状 | Maven 3.9+ 全面使用 maven-resolver 1.x |
独立的价值:解析逻辑与 Maven 解耦,任何工具可以复用同一套"仓库访问 + 依赖图 + 冲突解决"实现。
核心包结构
maven-resolver-api 接口定义(RepositorySystem、DependencyGraph...)
maven-resolver-impl 默认实现
maven-resolver-connector-basic 传输连接器(HTTP)
maven-resolver-spi 服务提供接口(仓库管理器、传输工厂)
maven-resolver-transport-http HTTP 传输实现
maven-resolver-transport-file file:// 传输实现RepositorySystem:统一入口
RepositorySystem 是 Aether 的门面接口,所有操作从它开始:
java
// org.eclipse.aether.RepositorySystem
public interface RepositorySystem {
// 依赖图构建
CollectResult collectDependencies(Session session, CollectRequest request);
// 解析构件(下载/定位文件)
ArtifactResult resolveArtifact(Session session, ResolveRequest request);
// 解析依赖(图 + 下载)
ResolveResult resolveDependencies(Session session, ResolveRequest request);
// 读取元数据
MetadataResult resolveMetadata(...);
}使用流程(编程方式)
java
// 1. 创建仓库系统
RepositorySystem system = ...;
// 2. 构建 Session(配置本地仓库、镜像、代理等)
DefaultRepositorySystemSession session = ...;
// 3. 构造请求
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(new DefaultArtifact("com.example:demo:1.0.0"));
collectRequest.setRepositories(repositories);
// 4. 收集依赖图
CollectResult result = system.collectDependencies(session, collectRequest);Session:解析上下文
Aether 的每个操作都携带 RepositorySystemSession,它携带全部上下文配置:
| 组件 | 作用 |
|---|---|
| LocalRepositoryManager | 本地仓库读写策略 |
| MirrorSelector | 镜像选择 |
| ProxySelector | 代理选择 |
| AuthenticationSelector | 认证选择 |
| ArtifactDescriptorReader | 读取构件 POM(依赖声明来源) |
| VersionResolver | 版本解析(SNAPSHOT/版本范围) |
| DependencySelector | 依赖选择(过滤传递依赖) |
| DependencyTraverser | 依赖图遍历策略(scope 处理) |
| RepositoryListener / RepositoryEvent | 事件通知 |
所有"策略"都以接口注入 Session,这就是 Aether 可扩展的核心:换一个 DependencySelector 实现,解析行为就不同。
依赖图构建与冲突解决
依赖图(DependencyNode 树)
collectDependencies 返回 DependencyNode 树:
根
├── node A(直接依赖)
│ ├── node A1(传递)
│ └── node A2
└── node B
└── node A2(冲突:与上面的 A2 版本不同)冲突解决:版本选择器
Aether 的冲突解决由 VersionSelector 与 DependencySelector 组合完成:
java
// 关键概念
VersionSelector:给定多个候选版本,选出"胜出版本"
DependencySelector:决定某个依赖节点是否进入图(过滤)Maven 默认组合:
DefaultDependencySelector(scope/optional 过滤)
+ NearestVersionSelector(最近版本选择)NearestVersionSelector 实现"最短路径优先":比较同一坐标各节点到根的路径深度,取深度最小者;深度相同则保留最先声明的。
冲突解决后的图形整理
仲裁后,Aether 提供 DependencyGraphTransformer 对图做变换(如扁平化、优化),Maven 用 ConflictResolver 生成最终依赖列表。
构件解析与本地仓库
resolveArtifact 流程
ResolveArtifactRequest(坐标 + 仓库列表)
│
▼
1. VersionResolver:把版本解析为具体版本(SNAPSHOT → 时间戳版本)
2. 计算构件路径(relativePath)
3. LocalRepositoryManager#find:本地仓库查找
│ 未命中
▼
4. 向远程仓库发起请求(connector-basic / transport-http)
├─ 下载构件文件
└─ 下载 .pom、校验和(.sha1/.md5)
5. 存入本地仓库(add 到 LocalRepository)
6. 返回 ArtifactResult(文件路径)本地仓库的布局
本地仓库根(如 ~/.m2/repository)
└── com/example/demo/1.0.0/
├── demo-1.0.0.jar
├── demo-1.0.0.pom
└── _remote.repositories(记录来源仓库)_remote.repositories 记录构件来自哪个远程仓库,用于仓库切换后决定是否重新拉取。
更新策略(UpdatePolicy)
- 普通 release 构件:本地存在即用(除非强制更新)
- SNAPSHOT 构件:按 updatePolicy 检查远程元数据(默认 daily;
-U强制) - 元数据缓存:
maven-metadata-*.xml有缓存时间控制
传输层:连接器与传输
RepositorySystem
└─ ConnectorManager(连接器管理器)
└─ RepositoryConnector(仓库连接器)
└─ 传输:HTTP / file / S3 / WebDAV...maven-resolver-connector-basic提供默认连接器maven-resolver-transport-http提供 Apache HttpClient 实现- 支持重试、超时、断点续传(HTTP Range)、并行下载
事件与监听
Aether 通过事件机制记录解析全过程,Maven 构建日志的下载信息即来源于此:
RepositoryEvent
├─ ArtifactDownloading / ArtifactDownloaded
├─ MetadataDownloading / MetadataDownloaded
├─ ArtifactResolving / ArtifactResolved
└─ 通过 RepositoryListener 分发bash
mvn compile -X # 开启 Debug,可见每次解析事件关键源码文件索引
| 类/接口 | 位置 |
|---|---|
| RepositorySystem | maven-resolver-api/src/main/java/org/eclipse/aether/RepositorySystem.java |
| DefaultRepositorySystem | maven-resolver-impl/.../DefaultRepositorySystem.java |
| DefaultDependencyCollector | maven-resolver-impl/.../DefaultDependencyCollector.java |
| NearestVersionSelector | maven-resolver-impl/.../DefaultVersionResolver.java |
| LocalRepositoryManager | maven-resolver-spi/.../LocalRepositoryManager.java |
| ArtifactDescriptorReader | maven-resolver-spi/.../ArtifactDescriptorReader.java |
常见问题
- Aether 与 Maven 的关系? Maven 的依赖解析逻辑已委托给 maven-resolver(Aether 的延续),Maven 核心只负责生命周期与插件编排。
- 怎么单独用 Aether? 引入
maven-resolver-api+impl+connector-basic+transport-http四个依赖,按 RepositorySystem 示例编程使用。 - 为什么慢? 多为 SNAPSHOT 检查远程元数据、或每次下载校验;配置 updatePolicy 与本地缓存可显著加速。