Maven依赖调解
Maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚的知道该传递性依赖是从哪条依赖路劲引入的。
例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都解析显然是不对的,因为会造成重复依赖,因此必须选择一个。Maven依赖调解的第一原则是:路劲最近者优先,该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
依赖调节第一原则不能解决所有的问题,如果两条依赖路径的长度是一样的呢?那到底谁被解析使用呢。Maven的依赖调解的第二原则:第一声明者优先。在依赖路径相等的情况下,在POM中依赖声明的顺序决定了谁会先被解析使用,顺序前的先解析使用。
可选依赖
假设有这样一个依赖关系,项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B、B->X(可选)、B->Y(可选)。根据传递性依赖的定义,如果所有这三个依赖的范围都是compile,那么X、Y就是A的compile范围的传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。换句话说,X、Y将不会对A有任何影响。
为什么要使用可选依赖这一特性呢?可能是项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时使用这两个特性,比如B是一个持久层隔离工具包,它支持多种数据库包括:MySQL,PostgreSQL等,在构建这个工具包的使用,需要这两种数据库的驱动程序,但是在使用这个工具包的时候,只会依赖一种数据库。
可选依赖的配置
上述XML代码片段中,使用<optional>
元素表示mysql-connect-java和postgresql这两个依赖为可选依赖,他们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递,因此,当项目A依赖于项目B的时候,如果其实际使用基于MySQL数据库,那么在项目A中就需要显示地声明mysql-connect-java这一依赖。
在理想的情况下,是不应该使用可选依赖的,而是应该为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,在各自的POM中声明JDBC驱动依赖,而且不使用可选依赖,用户则需要根据选择使用依赖。由于传递性依赖的作用,就不需要在声明JDBC驱动依赖。
==========================================================
情景再现:
项目A依赖于项目B,项目B依赖于项目C(v1), 项目A依赖于项目D,项目D依赖于项目E,项目E依赖于C(v2),
1、A--->B---->C(v1) ,
2、A------>D---->E----->C(v2)
项目A隐形依赖了两个版本的C,那到底采用哪个版本呢?
分析:
依赖调解第一原则:路径优先,很明显,第一种路径深度是3,第二种路径深度是4,所以,maven会采用C(v1)
依赖调解第二原则:声明优先,假设路径深度相等,那么声明在前的会被引用。
让我们继续思考,假设C(v1)、C(v2)版本,项目都不用,而是用C(v3),那么我们就用到了排除,
如下httpcomponent、poi各自排除codec,然后声明项目中需要的codec版本,exclusion只需要声明groupid、artifactid,不需要声明version,因为根据“依赖调解”的原则,一个项目中不可能出现groupid、artifactid相等而version相等的多个依赖。
exclusion,只会排除所在依赖项目中的依赖;就是说,我在httpcomponent声明了排除codec,并不会排除掉poi引用codec,要想poi中也排除,那么在poi中也声明排除
===============================================
归类依赖
关于Spring Framework的依赖有很多,他们来自同一个项目的不同模块。因此所有这些依赖的版本都是相同的,可以知道,如果将来想要升级Spring Framework的话,这些依赖版本也会一起升级。这一情况在java中也似曾相识,考虑代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这两个简单的方程式计算圆的周长和面积,有经验的程序员一眼就能看出问题,使用字面量3.14显然不合适,应该定义一个常量并在方法中使用,如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
使用常量不仅可以使代码变得更加简洁,更重要的是可以避免重复,只需要更改一处,降低了错误的概率。
使用maven属性归类依赖:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
这里简单使用到了Maven的属性,首先使用properties元素定义了Maven的属性,该例子中定义了一个springframework.version子元素,其值为2.5.6,有了这个属性之后,Maven运行的时候,会将POM中所有${springframework.version}
替换成2.5.6,也就是说,可以使用${}
的方式来引用Maven的属性,然后将所有Spring Framework依赖的版本值用这一属性引用。这个和在java中常量PI替换3.14是同样的道理,只是语法不同而已。
优化依赖
在软件开发的过程中,程序员会通过重构等方式不断地优化自己的代码,使其变得更简洁、更灵活。同理,程序员也应该能够对maven项目的依赖了然于胸,并对其优化,如去除多余的依赖,显式声明某些必要的依赖。
maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围。对于一些依赖冲突,也能进行调解,以确保任何一个构件只能唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
可以通过运行如下命令查看当前项目的已解析依赖:mvn dependency:list
将直接在当前POM声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经过Maven解析之后,就会构成一个依赖树,通过这颗依赖树,就能够很清楚地看到某个依赖是通过哪条路径引入的。可以运行如下命令查看当前项目的依赖树:mvn dependency:tree
使用mvn dependency:list
和mvn dependency:tree
可以帮助我们详细了解项目中所有的依赖的具体详细,在此基础上,还有mvn dependency:analyze
工具可以帮助分析当前项目的依赖