wusandi的技术博客 Java Coder For SDN

eclipse aether入门

2018-10-29
WuSanDi


一、Aether是什么

你曾经想要将Maven的依赖解析机制集成到你的应用中,而不用内嵌Plexus和整个Maven发布包吗?

你曾经想要以多线程方式使用Maven的依赖解析机制,但厌倦了有状态单例吗?

你曾经想要控制一下Maven如何计算解析依赖图,例如使用另一种策略解决冲突或 检查一些中间的依赖图吗?

那么,Aether就是答案。它是一个易于内嵌的与构件仓库一起工作的Java库,你能从远程仓库拉取构件用于本地消费,也可以将本地的构件发布到远程仓库与他人分享。

有很多种方式来传递构件、描述它们的关系以及使用它们。Aether在设计时就对这些方面的自定义进行开放,允许你增加甚至替换普通功能来满足你的需要。举例来讲,事实上,Aether核心不知道如何处理Maven仓库 。它是一个待定工具框架,提供一些通用的构件解析/部署框架,而将诸如仓库格式的细节留给扩展。

在这一点上,来自Apache Maven项目的 maven-aether-provider 可能是最有趣的扩展,它带来了对Maven仓库的良好支持。所以,如果你想要寻找一种消费中央仓库中的构件的方式,那么Aether结合Maven Aether Provider是你的最佳选择。这种方式使用Aether不仅简化处理构件的工作,而且确保与其它使用Maven仓库的工具的互操作性。

二、Aether传递依赖解析

Aether的一个重要任务时解析传递依赖。这个任务可以分为两个子任务:

  1. 确定组成传递依赖的构建的坐标
  2. 解析第1步中坐标标识的构件的文件

构件和它们的依赖相互形成依赖图。所以,换句话说,第1步就是要计算依赖图,第2步就是遍历依赖图,并下载图中的每一个构件。 在Aether中,这个依赖图很容易检测,提供的扩展点允许对依赖图的构建进行更多的控制。要理解那些扩展点,我们可以仔细看看依赖图的构建方式。

从给定的根依赖开始,例如 org.eclipse.aether:aether-impl:0.9.0,仓库系统首先读取对应的构件描述符(即处理Maven仓库的POM文件)。从构建描述符中,可以知道直接依赖、依赖管理和解析过程中应该考虑的额外的远程仓库。对于每一个直接依赖,依赖选择器都有机会将这个依赖从图中排除。如果包含这个依赖,那么依赖管理器就会应用依赖管理(如果有的话)。下一步,声明的依赖版本会被扩展成一个匹配版本号列表。对于简单的版本,如”1.0”,那么匹配版本号列表就只包含这个版本号。对于版本范围,如”[1.0,2.0)”,版本号列表通常包含多个版本号,受限于版本过滤器的过滤。 对于这个依赖的每一个匹配版本,子节点会被添加到依赖图中。每一个子依赖的递归过程由依赖遍历器控制。

上面的过程创建的依赖图经常包含重复的或者冲突的依赖,甚至循环依赖,这样的依赖图称为脏图。然后一系列的依赖图转换器就会被用于裁减这个图,形成解析图。

所以,从技术上讲,仓库系统返回给调用者的依赖图受下面这些实例影响:

org.eclipse.aether.collection.DependencySelector
org.eclipse.aether.collection.DependencyManager
org.eclipse.aether.collection.VersionFilter 
org.eclipse.aether.collection.DependencyTraverser
org.eclipse.aether.collection.DependencyGraphTransformer

当创建仓库系统会话时,仓库系统的用户可以提供满足它们自己的需求的实现,直接控制这些扩展点。例如,依赖选择器可以排除子依赖、排除可选的依赖或者某些不想要的作用域的依赖。依赖遍历器可以用于确定胖WAR包是否应该包含在依赖图中。版本过滤器可以排除构件的特定的版本,它们在当前上下文中不可接受,例如禁止snapshot。依赖图转换器可以辨识和标记脏树中的冲突节点,裁剪不想要的部分,来解析冲突版本或者作用域。

maven-aether-provider 中的类 MavenRepositorySystemUtils提供了一个会话模仿了Maven中使用的解析规则。如果你想要自定义图构建过程, 那么你可以自由的看一下那个类的源代码,学习实现类是如何获得Maven风格行为的,你可能想要在你自己的仓库系统会话中使用它们。Maven插件可以很容易的访问到当前的仓库系统会话,参见Aether用法了解实际的代码用法。

三、Aether依赖图

当解析传递依赖时,Aether会构造依赖图,它由 DependencyNode 实例组成,每一个节点表示一个依赖,它的直接依赖用子节点表示。在解析过程的早期阶段,通常存在重复依赖或者循环依赖,如下图所示:

   root
   / \
  /   \
a:1   b:1  <--+
  \   / \     |
   \ /   \    |
   c:1   a:2  |
    |         |
    +---------+

一旦这个依赖图经过冲突解析,即重复依赖被移除,那么就会得到一个依赖树。就前面的示例,树可能会是这个样子:

   root
   / \
  /   \
a:1   b:1
 |
 |
c:1

依赖树是一个方便的数据结构,可以获得完整的构件集,用于形成类路径等,因为一个简单的递归遍历就足以收集所有的相关的依赖了。

依赖图的故障排除

依赖树给用户提供了一个简洁的、基本工具来理解为什么/如何让构件在依赖中结束。但是,正如上面的例子说明的,与依赖图相比,依赖树丢失了一些信息。例如,依赖树无法表示 b:1 也依赖于 c:1.。为了排除复杂依赖图的故障,有一些配置属性可以让一些有用数据保留在 RepositorySystem.collectDependencies()返回的依赖图中。

例如,配置属性 ConflictResolver.CONFIG_PROP_VERBOSE 可以产生类似于m2e依赖层次的依赖图,它保留了冲突节点。这让终端用户对依赖中所有路径都有更好的了解。

配置属性 DependencyManagerUtils.CONFIG_PROP_VERBOSE 可以记录依赖的属性,在他们被依赖管理更新之前。这帮助用户理解为什么在依赖图中可以找到一个版本,而找不到另一个版本,或者以给定作用域结束。

请参考API文档,了解上述配置属性的细节,关于他们的作用和访问额外数据的方式。


Similar Posts

Comments