maven的dependencyManagement详解

蔚宏大
2023-12-01

 

背景

       最近接到一个jar包依赖统一管理的任务,提供一个类似于spring-framework-bom的pom管理项目(后续我称这个为pilot项目),在接到这个任务之前,对maven的熟悉程度只能说是会简单的使用,这次才发现,其实在使用过程中也是比较浑浑噩噩的,很多东西没有深入去了解和思考,导致的影响可能对于一个项目来说,编译和运行阶段不会出什么错(就算出错了,也能很快的排查掉),但是如果要对项目有严格要求和追求的话,可能就需要更细致学习。

PS:可能我遇到的问题比较小儿科我自己不懂,但都是我实际遇到的问题总结,大家勿喷!

存在的问题

       在这次任务的过程中并不顺利,本身mgr给我的时间是30天(包含周末),但我当时心里的想法是,这个事情需要这么长时间吗,我觉得我一个礼拜就能搞定,但幸好我没有说出来,目前我的任务状态还是没有上线,并且有一定概率会延期。我发现了很多项目都存在一个问题,试想下我们在使用maven的版本管理机制的目的是什么?是为了解决jar包冲突问题,这个事情的价值体现,也就是统一封装和管理一些有公共依赖的项目的jar包的版本,让项目结构,或者说你们的external libraries结构更清晰,减少pom结构代码的冗余,也可以结合项目原型去打造一个包含jar包管理的后端脚手架,而我在完成任务的过程中,发现大多数的项目都会引入一个pilot或者多个pilot,而且pilot里面包含的依赖,大多数的项目还会在自身的dependencyManagement再次引入,这种情况还不少,那这样就会导致几个问题

  1. 出现了冗余代码
  2. 违背了pilot的初衷
  3. 会给后来人产生误导
  4. 有可能因为这种冗余导致产生jar包依赖冲突的现象,我自己就遇到了,因为我自己cv的时候粗心大意导致的。

dependencyManagement

      maven语法提供的标签,用来统一管理jar包依赖的版本,但是不会引入依赖,只有当在某个模块中显示引入某个依赖的时候才会真正的引入jar包,以下是我在工作中踩的几个坑,也让我对这个标签的了解更加深入,其实这些坑都是围绕着同一个问题,当项目中直接或者间接存在同一个依赖但是不同的版本,maven最终采用的是哪个版本,有没有什么统一的规则,或者说同一个版本,但是因为同一个版本不存在差异性就显得没问题,这个也是我在过程中发现的比较多的问题。其实核心就是maven的就近原则

  • mvn dependency:tree

       这个命令后面可以加一些参数,比如输入mvn dependency:tree>tree.txt 可以将maven解析的依赖树写入到当前目录的指定文件中,其他的参数可以自行查阅,这个命令是解决和查看依赖最重要的命令,可以通过输出的内容找出某个jar包最终采用的是哪个版本,这个jar包是被哪个父依赖或模块引入进来的,方便定位和排查问题。但是这里有个注意点,被dependencyManagement管理的依赖,解析结果里不会指出当前依赖采用的版本是继承自哪里,也是我在工作过程中遇到的一个问题,下面会赘述。

  • 多个pilot的版本继承顺序

    <!--父项目/模块 -->      
    <parent> (Parent pilot)
        <groupId>com.xx.framework</groupId>
        <artifactId>xx-springboot-no-ark-plugin-parent</artifactId>
        <version>2.1.0</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <!--(A pilot) -->
            <dependency>   (A pilot)
                <groupId>org.springframework</groupId>
                <artifactId>spring-framework-bom</artifactId>
                <version>5.0.8.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
             <!--(B pilot) -->
            <dependency>   (B pilot)
                <groupId>com.xx.framework</groupId>
                <artifactId>xxx-xx-common-pom</artifactId>
                <version>2.1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
             <!--直接引入-一般处理父pilot管理的依赖版本不适用的情况,这种情况较少 -->
             <dependency>  (直接引入-一般处理)
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.3.14.RELEASE</version>
            </dependency>
    </dependencyManagement>

       

        我发现有很多项目在使用pilot的时候,是使用maven提供的parent标签的,pilot可以被作为parent引入也可以在dependencyManagement中被引入,个人建议放在parent中,因为parent可以覆盖propertites,后面会阐述。这个问题其实还有点奇怪,为什么会出现多个pilot的情况,我个人觉得出现多个pilot的原因只有2种,第一种就是开发人员懒,第二种就是开发人员不懂也不够细心。上面的项目不止同时引入了2个pilot,A polit是spring官方提供的一个开源bom项目,B polit是公司内部提供的一个pilot,其实B里面是包含了大多数常用的spring组件的,那是不是就至少会导致一个不生效,而且既然大多数常用的B polit都包含了,那其实没有特殊需求就不用引入A polit了,如果有特殊要求可以在dependencyManagement单独写出来,或者像B polit的开发提需求统一封装管理。

抛出一个问题,假设上述代码中的polit都引入了spring-context的依赖,每个引入的版本都不一样,那最终maven会采用哪个,规则是什么?

亲测优先级顺序:显示在dependencies中指定版本 > 本项目dependencyManagement直接引入(上述代码的4.3.14.RELEASE例子)> parent pilot > 其他pilot,其他存在多个pilot的按照编码优先级排序,比如上述代码A polit和B polit两者会优先采用A polit的版本,因为A polit写在前面。pilot内部如果依然存在上述问题,依然是按照此规则排优先级。

  • exclude不起作用问题

        由于在做此任务的过程中会有一个项目引入多个pilot的情况,所以这个坑也踩过,在dependencyManagement中type为pom scope为import的情况下,语法上是支持exclusions标签的,但是实际上是不起作用的,目前还没确定是设计如此还是算做一个bug。

  • mvn如何查看某个jar包的版本从哪继承的

        在介绍mvn dependency:tree这个命令的时候留了一个问题,也是我在工作中实际遇到的问题,我的任务是出一个polit项目,但是我是在以前的polit基础上去改的,以前的pilot项目已经有项目组在用了,我拿到了项目组的某个项目的权限,这个时候我去改了此项目中的pilot的版本,改成了我正在做的这个版本,发现mvn install过程中会报错,dependencies.dependency.version' for com.jayway.jsonpath:json-path:jar is missing,意思是这个依赖没有指定版本,所以install失败。可是我换成之前的版本就能成功,我比对了这两个版本之间的git代码差异,自始至终我的polit并没有管理这个依赖,那我就想看下这个依赖继承的是哪里的版本,可是mvn dependency:tree解析出来的结果不会显示版本是继承自哪里,只会显示依赖继承自哪里。这里我还进入了另一个知识盲点,比如A polit管理了B polit,但是在A中对B的管理并没有加scope和type标签,所以其实最终的结果就是哪怕B甚至是其他的CDEF...他里面也递归管理了各种dependencyManagement,都不起作用,可是我当时在做的时候的认知是起作用的,所以我为了找这个依赖的版本是谁管理的,我难道需要每一个依赖都循环递归去人工手动搜一遍吗,那工作量简直太大了!!!本着不懂就问的原则,幸好我问了身边的同事,告诉我了这点,那范围就缩小到了4个polit中搜索,而且不存在递归的问题,只要不到10秒就能搜索到,最终也确定了这个jar包的版本来自哪里的问题,然后根据实际情况判断这个jar包是否需要被纳入到polit中去管理。

步骤总结:去本项目全局搜索目标jar的名称,如果没有,去parent标签中的项目中搜索,如果还没有,去递归搜索dependencyManagement中带有scope=import和type=pom的项目中是否有指定,说是递归,一般都是一次循环就能找到。

  • 嵌套依赖的优先级问题

         父pilot又import了一个pilot ,里面有重复的依赖,不同的版本,采用哪个,上述多个pilot的版本继承顺序问题中已经阐述,会采用就近原则,谁在前用谁。

  • properties

        在工作中遇到了一个问题,现在基本上后端的java项目都是基于spring和springboot做集成开发的,所以依赖管理肯定是不能缺少spring的管理的,spring官方也提供了几套pilot管理的pom项目,但如果项目既使用spring官方的,又使用我的,那就会存在多个polit,可能会出现一些问题。spring和其他的普通包不太一样,他是一套生态,涉及到的组件和框架都比较多,版本迭代比较快,差异性可能也大,而且对于新老项目sping版本都采用的差距挺大的。刚开始的想法是不把spring纳入管理范围了,但是这样对于使用方来说特别不友好,不友好的地方在于,既然知道每个项目都会基于spring开发,那使用方既用了我的polit,又需要一个个手动引入spring相关的包,代码量和工作量会比较大。那就需要解决一个问题,我的polit中提供的spring的版本应该定哪个。在思考这个问题的过程中,我是不知道properties属性也是具有传递性的,但仅限于parent标签引用的pilot可以传递,dependencyManagement下面的不支持传递,所以这个问题困扰了我许久。但当我知道properties可以传递的时候,问题迎刃而解。

比如A项目引入了polit 1,polit 1的spring相关组件是5.x版本,但是A项目比较老,就想用4.x,这个时候,只需要在A项目中的properties属性中指定polit 1中的properties对应的spring版本号的property就行(注意:property标签的名称一定要和父polit的相同才能生效),maven会采用就近原则进行向下传递的,这样涉及改动的代码也只有一行。其他jar包也可以按照这个特性去处理差异化的地方。

  • 其他问题

        缺其他问题待补充

 公众号

 

 微信搜索“PPShare”,关注公众号。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 类似资料: