首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Maven 依赖调解源码解析(三):传递依赖,路径最近者优先

Maven 依赖调解源码解析(三):传递依赖,路径最近者优先

作者头像
xiaoxi666
发布于 2021-11-24 01:55:54
发布于 2021-11-24 01:55:54
65100
代码可运行
举报
文章被收录于专栏:xiaoxi666的专栏xiaoxi666的专栏
运行总次数:0
代码可运行

场景

A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。根据路径最近者优先原则,该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。

A 的 pom.xml 内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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">
    <parent>
        <artifactId>mavenDependencyDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>A</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>B</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>D</artifactId>
            <version>1.0</version>
        </dependency>


    </dependencies>

</project>

B 的 pom.xml 内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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">
    <parent>
        <artifactId>mavenDependencyDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>B</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>C</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

</project>

C 的 pom.xml内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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">
    <parent>
        <artifactId>mavenDependencyDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>C</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>X</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

</project>

D 的 pom.xml 内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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">
    <parent>
        <artifactId>mavenDependencyDemo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>D</artifactId>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>X</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

</project>

源码

刚拿到源码不知道从哪里打断点,我们可以先切换到模块 A 中,执行一下这个命令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 mvn dependency:tree -Dverbose

其中的verbose是为了输出详细信息,方便我们找到源码中的参照点。

可以发现输出为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[INFO] Scanning for projects...
[INFO] 
[INFO] ---------------------------< org.example:A >----------------------------
[INFO] Building A 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
[INFO] org.example:A:jar:1.0
[INFO] +- org.example:B:jar:1.0:compile
[INFO] |  \- org.example:C:jar:1.0:compile
[INFO] |     \- (org.example:X:jar:1.0:compile - omitted for conflict with 2.0)
[INFO] \- org.example:D:jar:1.0:compile
[INFO]    \- org.example:X:jar:2.0:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.002 s
[INFO] Finished at: 2021-11-20T12:17:29+08:00
[INFO] ------------------------------------------------------------------------

可以看到,A 依赖了 X(2.0),而 X(1.0)被忽略了。这句关键信息是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(org.example:X:jar:1.0:compile - omitted for conflict with 2.0)

那我们分别到apache-maven-3.6.3、maven-dependency-plugin 和 maven-dependency-tree这三个项目中找一下,看看是哪里输出的这句话。

我们最终在maven-dependency-tree这个插件项目中发现了这段输出的源头:

此时我们可以用前面讲述的调试方法(注意是调试插件maven-dependency-tree),在这里打断点,从而找出调用链:

很明显,这个 TreeMojo 就在 maven-dependency-plugin 项目中了:

结合前面在 maven-dependency-tree 中的调用链,可知是在TreeMojo#serializeVerboseDependencyTree 这个方法中,以访问者方式序列化依赖关系,这里面用到的visitor 是 BuildingDependencyNodeVisitor ,可以回到 maven-dependency-tree 项目查看调用链加以印证:

这一步明确之后,我们继续分析 maven-dependency-tree 刚才打断点的地方,看看为什么能进到这里,也即有哪些上下文。

我们发现,重点是 state 这个字段,因此看看哪里给它赋了值。

根据上图可以看出,只有一处赋值了 OMITTED_FOR_CONFLICT,点进去看看:

顺便可以看到:

1、如果重复声明的依赖版本号相同,那么 state 是 OMMITTED_FOR_DUPLICATE,意味着重复。

2、如果重复声明的依赖版本号不同,那么 state 是 OMMITTED _FOR_CONFLICT,意味着冲突,最终必然只会选其中的一个。

继续往上找调用链:

我们发现 omitForNearer 这个方法的定义在 apache-maven-3.6.3 这个核心项目中的 ResolutionListener 类中,而插件 maven-dependency-tree 中的 DependencyTreeResolutionListener负责了具体的实现。

那么我们中断此次调试,并在 omittedNode.omitForConflict( kept ) 这里打个断点,重新调试,看看上下文都有什么:

可以看到,要被忽略的依赖 和 要被保留的依赖,是由上层传入的。也就是说,apache-maven-3.6.3 这个核心项目已经做出了「应该保留哪个依赖」的判断

因此我们再次中断调试,回到 apache-maven-3.6.3 核心项目重新调试。

按照刚才的分析,我们找到 omitForNearer 被调用的地方,打上断点:

断点进来后,我们顺着调用链往上找,看看是在哪里决定的:

可以看到,是在 org.apache.maven.repository.legacy.resolver.DefaultLegacyArtifactCollector#recurse这个方法中决定的。看起来,似乎关键方法是 checkScopeUpdate( farthest, nearest, listeners )。我们需要点进去看看,它直接决定了哪个依赖被忽略,哪个依赖被保留。

我们再次重新调试。为方便,可以设置条件断点 "X".equals(((DefaultArtifact) nearest.artifact).artifactId),只关心 X 依赖。

可以看出,这个方法只是根据 scope 优先级进行处理,总而言之就是保留优先级更高的依赖。然而这并不是我们的场景。

因此,我们应该顺着调用链继续往上找。

重新调试(或者先回退调用栈,再前进),会发现进入到了这里:

可以看到,nearest 来源于 node,farthest 来源于 previous。而且这个赋值关系受到 resolved 和 previous 的相等关系控制。那我们分别看看 previous、 resolved 以及 node 的来源。

往上翻,可以看到 previous 也即X(1.0)和 node 也即 X(2.0)均是在上一步解析得到的:

而 resolved 是在这里解析得到的:

结合前面的分析,我们就可以知道:

如果 resolved 和 previous 相同,那么保留 previous,忽略 node;反之,保留 node,忽略 previous。

那我们需要看看这行代码内部究竟干了啥:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
resolved = conflictResolver.resolveConflict( previous, node );

调试进入:

可以看出我们进入了 NearestConflictResolver 这个冲突调解器,具体地,它会选择路径最近的依赖。从实现层面看,非常简单:它直接比较两个依赖的路径深度,发现 X(1.0)的深度为3,X(2.0)的深度为2,按照规则,需要保留路径深度更小的 X(2.0)。

那么问题来了,什么时候会调用 conflictResolver.resolveConflict( previous, node ) 呢?看下图:

处理完这一步之后,会把X(2.0)也加入previousNodes中:

还有个小尾巴,上面我们提到: previous 也即X(1.0)和 node 也即 X(2.0)均是在上一步解析得到,让我们看看:

其实很容易发现,依赖的解析过程就是一种深度遍历,这里的 recurse 方法会被不断递归。用我们的例子来理解,先遍历了A->B->C->X(1.0),然后遍历了 A->D->X(2.0) ,我们刚刚调试的过程正处于 D->X(2.0)刚刚完成的时刻。

好了,当这些递归遍历结束后,返回到org.apache.maven.repository.legacy.resolver.DefaultLegacyArtifactCollector#collect 方法,准备生成结果:

可以看到,只有 isActive 的依赖才会被收集到结果中,也就是最终起作用的依赖版本。其实 active 的设置就是在之前的这个步骤实现的:

可以看到,如果一个依赖被忽略,它本身的所有依赖也会被忽略。

小结

至此,我们已经知道了路径最近者优先原则的运行原理:依赖的解析是一种深度遍历的过程,每当解析一个依赖后,均会将其放到 resolvedArtifacts 这个Map中,后续再看到同名的依赖时,进行冲突调解。对于路径最近者优先原则来说,具体的冲突调解器是NearestConflictResolver。

扩展一下:上述分析过程中,我们看到了 ConflictResolver 这个接口,发现它是专门进行依赖调解的,不同的调解方式应该就是由具体的实现类来处理。对于路径最近者优先原则来说,就是由 NearestConflictResolver 处理。那其他的原则会由其他的依赖调解器处理吗?

就让我们看看都有哪些具体的依赖调解器:

上图结合源码可以看到,总共有4种调解器,分别是:

  • 版本最老者优先
  • 版本最新者优先
  • 路径最近者优先(还有一个默认调解器继承了它,但实现是空的,已经被打了 @Deprecated 标记,可以不考虑)
  • 路径最远者优先

回到刚才的调解过程可以看到,默认调解器是「路径最近者优先」:

所以可以猜测,本文中其余的原则应该没有使用其他的调解器,它们应该是在某些插件中起作用的。比如Maven 有插件可以将版本更新到最新,应该就是用了 NewestConflictResolver 这个版本最新者优先的调解器,本文不再探索,有兴趣的读者可自行研究。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-11-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
工具|解析迁入一键搞定是什么体验?
关注阿D了吗?点蓝字加关注 说起域名解析,D友们应该都不陌生,毕竟D家是靠为大家提供稳定、长期免费的解析服务起家的。 现已运行13年,虽然中间有一段时间是被放养的野孩子,但去年它的亲papa-DNSPod创始人-奶罩又重新回归领导。这次回来冲劲很足,势头很猛,当然带来了不少鲜货干货。DNSPod也开始茁壮成长、不断地改善更新,力图为大家提供一个更加稳定、高效的DNSPod产品服务生态。 所以最近,闻听风声的小伙伴们又(you)双(shuang)叒(ruo)叕(zhuo)跑来问阿D:“你们的解析服务在行业快
腾讯云DNSPod团队
2023/05/04
5860
工具|解析迁入一键搞定是什么体验?
国内外提供免费的域名DNS解析的服务商
是不是该来的时候了!CloudXNS将很快停止免费用户的域名解析!还是世界上没有“永久”的免费服务?! 哪个适合我们的域名?让我们给你推荐一些。 博客作者计划将xtboke.cn域名放入域名。博客部署在滕循云,域名解析也由滕循云~ 获得 也有很多老板把域名放入华为的云中~ ~ 我们可以根据以下几点进行选择。 1)您网站的用户群是国内的还是国外的? 2)域名是否存档? 3)域名是否已被工业和信息化部列入黑名单? 4)国内域名系统为国内提供智能线路划分。 5)它是合法网站吗? 6)参考服务提供商的规模。 7)服务提供商提供高级功能。我们能使用它们吗?
Erwin
2020/01/02
30K0
小白搭建博客教程-域名解析(3)
2016-06-1015:56:17 发表评论 1,115℃热度 上一回说道:小白搭建博客教程之流程,这次趁着端午节写完它,也算是一个了结。 教程有4篇: 购买域名; 购买虚拟主机并搭建(Linux)
timhbw
2018/05/03
3.7K0
Let's Encrypt 免费泛域名证书(野卡)申请使用方法:
今天Let's Encrypted泛域名证书终于正式上线,Let's Encrypted提供的泛域名证书跟其之前提供的单域名证书一样,90天续期一次,但是有一点不同的是,其泛域名证书的申请需要通过ACME V2来申请。 官方发布地址: https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579 支持ACME的客户端: https://letsencrypt.org/docs/client-options/
徐大嘴
2019/03/21
4.2K0
Let's Encrypt 免费泛域名证书(野卡)申请使用方法:
11个国内外免费域名解析服务
 一般域名使用注册商提供的域名解析服务虽然方便,但功能大多有限,特别是目前国内还会针对某些DNS服务器进行屏蔽,造成网站无法解析的情况出现,因此,使用第三方域名解析服务也是中国网站的必要选择,这里就介绍一些常见的免费域名解析服务。 域名注册商提供的免费服务 Godaddy:不在Godaddy注册域名,也可以使用Godaddy的域名解析服务,使用方法很简单,登录Godaddy网站后,点击“Add Off-site DNS”即可添加用户的域名,之后将用户域名的DNS设置为Godaddy指定的地址,域名DNS生效
小小科
2018/05/02
27.6K1
有奖|脱单指南,一页搞定!
点击蓝字关注我们 不瞒大家说,阿D的上一任npy还停留在一年前一个风清气爽的傍晚。 于是面对日益迫近的一(依)九(旧)七(虐)夕(狗)节,阿D流下了没有对象的泪水。 不用大家说,阿D自己十分清楚:俺这么帅,作为全村的希望,不能正常的过个七夕,简直不合理。 所以秉着为大家以身试法的精神和强烈的脱单欲,阿D看了市面上一个又一个七夕适配产品。什么口红项链、什么玫瑰郁金香、什么存钱罐(纳尼??提醒对方省钱吗?等着挨批吧。)。。。都太过寻常了吧? 寻寻觅觅,直到看到这样一条帖子: 这个时候阿D毅然决然想做
腾讯云DNSPod团队
2023/05/04
7370
有奖|脱单指南,一页搞定!
干货|建站流程之域名解析
点击“蓝字”关注我们 最近看到很多小伙伴在求教建站流程。索性阿D也去试了试,现带大家走一遍部分流程吧 。 首先,根据部门程序XGG的暗示: 实际访问网站时,确定从哪台服务器获取网页,对于计算机来说是通过IP地址实现的。由于IP地址是数字形式,为了便于人们记忆,所以使用了域名和网址机制。 域名—>IP地址:在访问网络时,网址会被发送到DNS服务器,然后由DNS服务器返回我们要访问的服务器IP地址,从而进行访问。这是DNS的工作原理,也是之所以需要设置域名解析的原因。我们要在DNS服务器上设置域名解析,让域名
腾讯云DNSPod团队
2023/05/04
1.8K0
干货|建站流程之域名解析
[免费域名]免费撸.tk、.ml、.gq、.ga、.cf域名亲测教程
看到很多友友还在用别人的二级域名,要想自己的网站让更多人访问,域名也是一个很关键的,好的域名一天什么都不干都有流量。所以赶快去撸几个简短,好看,炫酷的一级域名吧,不要落后了,说不定以后这域名很值钱呢。
Youngxj
2018/06/07
55.1K7
【DNS 解析】DNS解析功能你真的会用吗?
每位站长在用dnspod进行域名解析的时候,有没有好奇过“记录类型”到底是干什么的?他究竟有什么用,那么多解析类型分别是干什么的?
MGS浪疯
2022/06/04
94.6K2
免费的动态域名解析(ddns)的申请
ddns,又称动态域名解析,适用于没有固定IP的网络,通过ddns,即动态域名解析,
风吹屁屁凉
2021/07/14
89.8K7
DNSPod x QQ音乐,守护周杰伦“奇迹现场重映计划”千万级线上直播
TME live周杰伦地表最强摩天伦-“奇迹现场重映计划”之回首篇将于5月20日、5月21日在QQ音乐线上播出,截至目前直播总预约人数已达1476万。 而QQ音乐,也是DNSPod 的用户,通过DNSPod的产品HTTPDNS ,降低域名解析时延,保护数据传输安全、防劫持,给予粉丝们更好的视听盛宴。 那么本周的D妹小课堂,就给大家讲讲,面对像虎牙、TME live这类直播需求的客户,HTTPDNS是如何帮助这类APP 优化时延、保障访问安全的问题。 HTTPDNS 改善直播、点播推拉流慢、卡顿问题 用户痛
腾讯云DNSPod团队
2022/05/23
2.9K0
DNSPod x QQ音乐,守护周杰伦“奇迹现场重映计划”千万级线上直播
通过acme.sh开源工具申请泛解析SSL证书
2.Linux云服务器(本文系统某讯云的Centos7),测试公网IP为:150.158.130.33
星哥玩云
2022/06/28
5020
通过acme.sh开源工具申请泛解析SSL证书
D妹爆料:CDN 域名解析加速功能即将上线,时延最少下降50%
近些年随着5g、云计算、AR技术的发展,用户对音频、视频、图片等静态资源加载速度的要求越来越高。企业为了提高静态资源的加载速度,逐步将大部分业务内容放到了CDN上。 但是,在使用CDN 过程当中可能会遇到CDN 域名解析时延过长的情况。不仅会直接影响网站/APP的用户体验,甚至会影响数据传输的安全性。 那么如何降低CDN 域名解析时延呢? 腾讯云DNSPod 推出的CDN 域名解析加速功能,近期会上线。 上线后,CDN 域名解析时延最少下降50%!! 再介绍新功能之前,D妹先给大家讲讲,我们目前的CDN
腾讯云DNSPod团队
2022/05/23
3.9K0
D妹爆料:CDN 域名解析加速功能即将上线,时延最少下降50%
利用DNSPod实现动态域名解析DDNS (解析内网、外网或IPV6地址)
2020-01-03 – 修复了12月31日由于 dnspod API 改动导致的失效。
全栈程序员站长
2022/08/13
29.6K4
利用DNSPod实现动态域名解析DDNS (解析内网、外网或IPV6地址)
双十一活动购腾讯云轻量云免费申请ssl证书,给你的应用加一道安全锁
之前介绍了《腾讯云轻量应用服务器部署私有笔记》,https://cloud.tencent.com/developer/article/2466047 搭建了自己的私有笔记系统,但是没有申请ssl证书,没有SSL证书的网站数据传输不被加密,这使得登录凭证、个人信息、支付详情等敏感信息容易被第三方拦截和窃取,增加了数据泄露的风险。
星哥玩云
2024/11/19
7830
双十一活动购腾讯云轻量云免费申请ssl证书,给你的应用加一道安全锁
使用acme.sh申请Let's Encrypt免费的SSL证书
acme.sh 实现了 acme 协议,可以从letsencrypt生成免费的证书。接下来将为大家介绍怎样申请Let's Encrypt通配符证书。
青阳
2021/09/15
6.1K4
完美,竟然用一个脚本就把系统升级到https了,且永久免费!
现在很多站长都会考虑将自己的站点从http升级到https,不仅是基于安全的考虑,有的也是因为第三方平台的限制,如谷歌浏览器会将http站点标记为不安全的站点,微信平台要求接入的微信小程序必须使用https等。
IT咸鱼
2025/05/20
610
完美,竟然用一个脚本就把系统升级到https了,且永久免费!
每粒尘埃都有IP,你信了吗?
戳上面的蓝字关注我们哦! 前段时间,大家有没有受到IPv6升级新闻的地毯式轰炸?那段时间似乎什么都能扯上IPv6,就像: 以至于现在每个人听到IPv6都很熟悉,但又说不出它到底是什么。 那它到底是什么呢? 简单来说,它是能使连接到网上的所有计算机网络实现互相通信的一套规则。 说人话就是,你要通过一个连接到互联网上的终端发送QQ消息给你心仪的女孩子,那发送和接收这条消息的两个终端都得遵循IP协议,才有可能进行传输。 当然,整个传输过程可不止这么简单,要遵从包括IP协议在内的,TCP协议、http协议、dn
腾讯云DNSPod团队
2023/05/04
1340
每粒尘埃都有IP,你信了吗?
D+解析移动APP的利器
3月27日,国内最大域名智能解析服务商DNSPod正式推出移动解析服务,名为“D+”,填补了目前国内市场上关于HttpDNS的这一空白。(https://www.dnspod.cn/httpdns) DNSPod技术负责人介绍称,“D+”是为移动而生的专业解析服务,它的诞生就是为了解决移动解析常见的“域名劫持”和“解析结果跨域”两项难题,让客户以最小的改造成本,解决移动业务解析异常的问题,并满足流量精确调度的需求。 根据CNNIC官方报告数据显示:“截至 2014 年 12 月,中国手机网民规模达 5.57
腾讯云DNSPod团队
2023/05/04
1.1K0
D+解析移动APP的利器
TLS 以及自动更新证书: Let's encrypt + acme.sh
Mr. Hsu edited this page on 18 Jun · 24 revisions
OwenZhang
2021/12/08
1.8K0
推荐阅读
相关推荐
工具|解析迁入一键搞定是什么体验?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档