Aether使用指南(主体功能概述)
相关文章:《Aether在Android中的适配探索》
本文所用Aether版本为1.1.0。
Get Started
Aether作为一个具有依赖注入(Dependency Injection)设计思想的库,它的主体System只能接受所依赖工具的interface实现,所以我们在调用前需要完成所需工具类的初始化(或是指定类,由Aether进行主动实例化)。
Aether提供了org.apache.maven.repository.internal.MavenRepositorySystemUtils
工具类以方便快速配置,虽然但是,我们仍需创建一个Factory来封装完整的初始化逻辑。
1 |
|
完成初始化逻辑封装后,假如需要下载一个远程依赖(例如,org.apache.maven:maven-profile:2.2.1
)我们只需:
1 |
|
运行后,便可在设置的本地仓库(Local Repository)文件夹下找到已下载的依赖库文件。
具体代码的内容也很清晰易懂:
首先,Factory完成的是仓库连接与传输、本地仓库缓存的配置设置;
在main()方法内,初始化system(Repository系统及其功能的主要入口点)以及session(定义控制Repository系统的设置和组件),再创建一个Dependency(需要下载的依赖);
依赖必定需要从MavenRepository内查找,所以我们还需要RemoteRepository作为指定的远程仓库(注意:RemoteRepository可以同时添加多个,会依次查找);
进行Dependency的下载前,需要构建其依赖关系树,以下载其及其所需子依赖,构建依赖关系树前需要查询,则下一步进行的是CollectRequest,设置其rootDependency为所需依赖(必定为依赖树的root),并添加需要查找的RemoteRepository;
下一步即为依赖树的构建,repoSystem.collectDependencies()获取DependencyResult,再调用DependencyResult.getRoot()获取树的根节点,得到依赖树;
DependencyRequest用于初始化一个Dependency下载请求,设置root为刚刚得到的依赖树根节点即可,调用RepositorySystem.resolveDependencies(session,dependencyRequest)即可进行下载工作。可能会比较疑惑下载到哪里了,实际上可以知道在newSystemSession()方法内,我们进行session的初始化时已经设置了LocalRepository作为本地仓库,即缓存文件存放位置。
Tips: 最后的PreorderNodeListGenerator实质上不会对依赖下载产生影响,只是官方提供的一个遍历依赖树的示例。
Aether开发逻辑介绍
API部分建议自行浏览Aether的源代码,只需要了解基本的开发逻辑,则根据名字以及注释很容易上手。
- Aether使用的是依赖注入(Dependency Injection)这一设计模式,在使用RepositorySystem之前,必须从DefaultServiceLocator中进行初始化。DefaultServiceLocator在初始化时便以默认配置了需要类,我们按需添加缺失的必要类即可。在完成配置之后,Aether会通过DefaultServiceLocator.getService()方法得到需要的interface(当然是已经实例化的),所以我们可以通过addService()在不修改Aether代码的情况下,更改一些核心逻辑,实现高度的自定义化;同时需要强调的是,我们也应该通过DefaultServiceLocator.getService()进行所需interface的获取。
- Aether的所有操作基本上都是先创建一个XxxRequest,然后通过System执行对应的操作逻辑,结果是返回一个XxxResult。例如,Get Started代码中main()方法内的CollectRequest和DependencyRequest。且XxxRequest为Final Class,自行初始化即可。
- 特别注意,RepositorySystem需要通过DefaultServiceLocator得到的,通过依赖注入,RepositorySystem会自动获取DefaultServiceLocator中所需要的类;RepositorySystemSession则是配置信息的载体,需要自己通过代码设置配置信息。
常见操作需求
下载Dependency
见 Get Started 不再赘述。
获取某一Artifact的缓存路径
- 通过RepositorySystemSession.getLocalRepositoryManager()获取LocalRepositoryManager;
- 调用LocalRepositoryManager.getPathForLocalArtifact()
常见Model的初始化
- Artifact
调用DefaultArtifact的构造方法即可,需要传入coords(坐标,诸如org.apache.maven:maven-profile:2.2.1格式),也有其他构造方法,具体自行浏览API。 - Dependency
调用Dependency的构造方法,并传入Artifact及scope(Maven Scope)等参数即可。
常见问题(已解决)
无法自动识别依赖文件类型为AAR的Artifact
因为我的初衷是用在android ide上,下载aar文件是不可避免的,但是从org.eclipse.aether.artifact.DefaultArtifact
的构造方法可以看到(见下面的代码),默认的extension是jar,并且extension是final修饰的。
1 |
|
可能到这里会想,那就在coords里指明extension不就可以了吗?很遗憾,测试后,你会发现这个根依赖倒是下载了,但是其子依赖仍然无法正确的识别文件类型。
接下来我们从源码的层面分析一下:
首先,根据上文的研究,Collect阶段会进行依赖树的构建,我们以此为入口,分析子依赖的构建过程。需要注意的是interface对应的默认impl类为DefaultXxx,比如RepositorySystem对应的默认impl类为DefaultRepositorySystem。
1 |
|
可以看到dependencyCollector.collectDependencies()执行了具体的操作,从dependencyCollector的初始化代码以及DefaultServiceLocator的相关代码,得知默认impl类为org.eclipse.aether.internal.impl.collect.DefaultDependencyCollector,继续往下追踪。
1 |
|
好好好,继续套娃操作,看代码知默认是深度优先org.eclipse.aether.internal.impl.collect.df.DfDependencyCollector
,并且DependencyCollectorDelegate会利用collectDependencies()包装了DependencyCollector.doCollectDependencies()方法。
1 |
|
1 |
|
代码中已用注释写明了关键代码位置,可以得知在RootDependency的Collect操作中,会进行ArtifactDescriptor(即,依赖的pom文件的解析)的解析操作,得到该Dependency的直接依赖,此操作由ArtifactDescriptorReader进行,但是既然解析了pom文件,为什么不根据pom文件内声明的extension类型来修正Artifact对应的文件类型呢?只因你太美Artifact中的extension是final修饰的,咱也不懂Aether为什么不写个自动修正extension的操作,毕竟人家是apache foundation下的,咱也不敢问。
解决方法
前情回顾,Aether内部使用了Dependency Injection,我们如果需要更改内部的逻辑,最简单的当然还是利用DefaultLocatorService操作了。
我们只需这么修改代码,将默认的ArtifactDescriptorReader更改为可以自动修正的自定义ArtifactDescriptorReader即可:
1 |
|
这个思路显然是对的,但是问题又来了,我们如何实现自动修正,毕竟人家extension是final修饰的?反射操作。
那,又怎么获取正确的extension呢?不妨来看看ArtifactDescriptorReader的代码,看看人家是怎么解析的。
org.apache.maven.repository.internal.DefaultArtifactDescriptorReader
的代码摘要(具体片段懒得继续缩减了,就这个方法先凑活看好了):
1 |
|
上面的loadPom()方法会在DefaultArtifactDescriptorReader.readArtifactDescriptor()中调用,代码中的org.apache.maven.model.Model
类是POM文件的模型类,我们可以调用Model.getPackaging()获得extension,并且loadPom()接受一个ArtifactDescriptorResult参数,我们可以通过ArtifactDescriptorResult.getArtifact()取得当前操作的Artifact以更正extension字段。
Q:为什么我不使用setArtifact()方法来替换Artifact?
A:因为,lazy不想再实验了,并且用反射得到的程序可以正常运行(逃)。如果感兴趣可以自己试试
注意:代码中存在internal的类,我们不能在自己项目内直接调用,所以再加一层反射操作来替换这些操作internal的类即可。
CompactAARArtifactDescriptorReader代码如下(随手写的,建议用者再仔细看看,另外Reflect为团队内的反射工具,自己替换为正常的反射代码即可):
1 |
|
提示:千万不要忘记在Factory内替换DefaultArtifactDescriptorReader为CompactAARArtifactDescriptorReader。
Aether的版本冲突解决方式与Gradle的不同
假如,使用已经兼容AAR依赖的Aether系统去下载Google的一些库,比如androidx
系列,很大概率出现因为依赖冲突而无法下载的问题,或者即使下载了也无法正常编译。
这个问题是因为Aether和Gradle的默认版本冲突解决方式不同,Aether是就近原则,依赖程度浅的版本则优先度高,Gradle则是就近+最新原则,在本地配置内采用最近原则,在处理远程依赖的子依赖时采用最新版本。
解决方法
我们只需要改变依赖冲突的解决方式即可,这个需要通过RepositorySystemSession来设置。
1 |
|
自己实现所需的依赖树裁枝方式即可。我这里比较懒省事,直接做一个LazyConflictResolver,下载依赖树上的所有依赖,之后在编译的时候取最新的版本。
1 |
|