一. 依赖的配置
依赖配置主要包含以下元素:
- groupId、artifactId和version:依赖的基本坐标
- type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar。
- scope:依赖的范围
- optional:标记依赖是否可选
- exclusions:用来排除传递性依赖
注:大部分依赖声明只需要包含基本的坐标。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 <!--添加依赖配置--> 2 <dependencies> 3 <!--项目要使用到junit的jar包,所以在这里添加junit的jar包的依赖--> 4 <dependency> 5 <groupId>junit</groupId> 6 <artifactId>junit</artifactId> 7 <version>4.9</version> 8 <scope>test</scope> 9 </dependency> 10 <!--项目要使用到Hello的jar包,所以在这里添加Hello的jar包的依赖--> 11 <dependency> 12 <groupId>me.gacl.maven</groupId> 13 <artifactId>Hello</artifactId> 14 <version>0.0.1-SNAPSHOT</version> 15 <scope>compile</scope> 16 </dependency> 17 </dependencies>
二. 依赖管理
2.1 依赖范围
首先需要知道,Maven在编译项目主代码的时候需要使用一套classpath。比如一个spring项目中,编译项目主代码的时候需要用到spring-core,该文件就会以依赖的方式被引入到classpath中。其次,maven在编译和执行测试的时候会使用另外一套classpath,比如junit,它会以依赖的方式引入到测试使用的classpath中,不同的是,这里的依赖范围是test。最后,实际运行maven项目的时候,又会使用一套classpath,比如spring-core需要在这个classpath中,但是Junit去不需要。
依赖范围scope用来控制依赖和编译,测试,运行的classpath的关系. 主要的是三种依赖关系如下:
- compile: 默认编译依赖范围。对于编译,测试,运行三种classpath都有效
- test:测试依赖范围。只对于测试classpath有效
- provided:已提供依赖范围。对于编译,测试的classpath都有效,但对于运行无效。因为由容器已经提供,例如servlet-api
- runtime:运行时提供。例如:jdbc驱动
注:当然还有一些别的不怎么常用的依赖范围,比如system,import等。
2.2 传递性依赖
2.2.1 传递性依赖介绍
MakeFriends.jar直接依赖于HelloFriends.jar,而HelloFriends.jar又直接依赖于Hello.jar,那么MakeFriends.jar也依赖于Hello.jar,这就是传递性依赖,只不过这种依赖是间接依赖,如下图所示:
2.2.2 传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。假设A依赖于B,B依赖于C,我们就说A对于B是第一直接依赖,B对于C是第二直接依赖。A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。依赖范围影响传递性依赖有如下关系:
说明:第一列是第一直接依赖,第一行是第二直接依赖,除此之外就是传递性依赖的范围。
可以发现这样的规律:当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;当第二直接依赖的范围是test的时候,依赖不会得以传递;当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided;当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。
2.3 依赖调解
虽然Maven引入了传递性依赖机制帮助我们只需要关注直接依赖,但是,正是因为传递性依赖的引入,会有一些注入的版本升级等等不兼容问题需要我们来解决。我们需要清楚的知道该传递性依赖是从哪条依赖路径引入的。
2.3.1 第一原则-----路径最近者优先
比如:项目A有这样的依赖关系:A--->B--->C--->X(1.0)、A--->D---->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?(只可以选择一个)。Maven依赖调节的第一原则是:路径最近者优先,因此X(1.0)到A的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
2.3.2 第二原则-----第一声明者优先
比如:A---->B---->Y(1.0)、A---->C----->Y(2.0),依赖路径长度一样。这个时候就需要使用Maven的依赖调解的第二原则:第一声明者优先。比如B的依赖声明在C之前,那么Y(1.0)就会被解析使用。
2.4 可选依赖
假设有这样一种依赖关系:A依赖B,B依赖X或者Y。由于X和Y是可选的,因此依赖将不会得以传递,也就是说X、Y将不会对A有任何影响。
2.4.1 为什么要使用可选依赖
可能项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,包括Mysql、PostgreSql等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但是在使用这个工具包的时候,只会依赖一种数据库。
可选依赖
项目B的依赖声明如下:
说明:使用<optional>元素标识mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其它项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于Mysql数据库,那么在项目A中就需要显示地声明为mysql-connector-java这一依赖。见下:
注:在理想的情况下,是不应该使用可选依赖的。使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则。这原则同样适用于Maven。因此,在上面的例子中,更好的做法是为Mysql和PostgreSql分别创建一个Maven项目,用户则根据需要选择使用这两个中的哪一个maven项目依赖。
2.5 排除依赖
传递性依赖会给项目隐式地引入很多依赖,这极大的简化了项目依赖的管理。但是有的时候这种特性也会代理问题。例如:当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。
举例:
比如项目a依赖项目b,但是由于一些原因,不想引入传递性依赖c,而是自己显示地声明对于项目C 1.1.0的依赖。采用了exclusion元素来排除。
note:声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。
2.6 归类依赖
在升级Spring Framework时候,它所依赖的版本(core,beans等等)会一起升级。倘若我们在pom中每个jar写了版本,很容易写错,造成版本升级不兼容的问题。为了避免这种现象,可以采用JAVA编程当中的常量代替字面量的方式来解决。统一定义一个依赖版本,然后在不同的地方来引用就好了。
说明:首先使用properties元素定义Maven属性,然后在原型时候会将pom中的所有${springframework.version)替换成实际的值。
三. 优化依赖
3.1 查看依赖树
通过前面的学习,程序员可以对Maven依赖进行优化,比如去除多余的依赖,显示的声明某些必要的依赖。
Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构建只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖。
可以运行如下命令查看当前项目的已解析依赖: mvn dependency:list
(前提:假设pom中配置了spring core和junit)
当这些依赖经过Maven解析后,就会构成一个依赖树,通过这棵树就能很清楚地看到某个依赖是通过哪条传递路径引入的。
可以运行如下命令查看当前项目的依赖树: mvn dependency:tree
说明:上面的依赖树种可以看到spring-jcl也引入了,这是因为spring-core依赖了它。并且可以看到依赖范围。
3.2 优化分析依赖
通过dependency:list和dependency:tree可以帮助我们详细了解项目种所有依赖的具体信息,在此基础上,还有dependency:analyze工具可以帮助分析当前项目的依赖。
比如说项目中显示引入了spring-context和spring-context-support。现在删除spring-context,会发现项目编译、测试和打包都不会有任何问题。通过分析依赖树,可以看到spring-context是spring-context-support的依赖,因此会得以传递到项目的classpath中。先运行:mvn dependency:tree
再运行mvn dependency:analyze
分析:该结果中重要的是两个部分:
(1)Used undeclared dependencies,意指项目中使用到的,但是没有显示声明的依赖。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多关于Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。因此,需要显示声明任何项目中直接用到的依赖。
(2)Unused declared dependencies,意指项目中未使用的,但显示声明的依赖,比如spring 某个项目中出现了这种依赖:有spring-core和spring-beans。这种依赖我们不应该简单的直接删除,应该仔细分析。因为dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。很显然,该例中spring-core和spring-beans是运行spring frameworkd项目必要的类库,因此不应该删除依赖声明。但是有时候,确实是需要通过该信息找到一些没有用的依赖,但是一定要小心测试。
参考文献
《Maven实战》
https://www.cnblogs.com/xdp-gacl/p/4051819.html