依赖传递
大家要理解一个问题:Maven仓库中的所有jar,其实本质上都是一个Java项目,只是打成jar包放到Maven仓库中而已,既然是Java项目,那么这个项目可能也会用到一些第三方的jar包。
当我们引入某些jar包的时候,会把这些jar包依赖的jar包同样引入进来,这就是依赖传递。
例如有个Commons-logging项目,项目Spring-core依赖Commons-logging,而项目user-service依赖Spring-core。那么我们可以说 user-service也依赖Commons-logging。也就是说,依赖的关系为:user-service—>Spring-core—>Commons-logging
当我们执行项目user-service时,会自动把Spring-core、Commons-logging都下载导入到user-service项目的jar包文件夹中,这就是依赖的传递性。
dependency完整结构:
<dependencies>
<dependency>
<groupId>组织/父项目名称</groupId>
<artifactId>项目名称</artifactId>
<version>项目版本号</version>
<type>依赖的类型,对应项目的packaging,默认是jar</type>
<scope>依赖范围</scope>
<systemPath>配合 scope=system 时使用</systemPath>
<optional>标记是否为可选依赖</optional>
<exclusions>
<!-- 用来排除传递性依赖 -->
<exclusion>
<groupId>组织/父项目名称</groupId>
<artifactId>项目名称</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
通过设置<optional>true</optional>,表示该依赖是可选的,不会被依赖传递。
举例验证:
引入依赖spring-core。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
结果如下:
仔细观察,还发现此时还多了commons-logging:commons-logging:1.2,这就涉及到了“依赖”。
当A jar包需要用到B jar包中的类时,我们就说A对B有依赖。当前工程会到本地仓库中根据坐标查找它所依赖的jar包。
我们通过Maven仓库可以发现,spring-core编译的时候依赖了5个jar,但是最终依赖传递进来的只有一个,原因在于配置了optinal标签。
注意:optional标签只能在自己的项目中设置不向下传递(如果我们把项目打成jar包,我们项目的中的某些依赖不向下传递),如果不想使用要别人项目传递进来的依赖,可以使用依赖传递的排除。
依赖传递的排除
有时候为了确保程序正确,可以将有可能重复的间接依赖排除。
依赖传递的排除的排除分为两种:
1、通过配置文件排除依赖。
2、Maven自动排除重复依赖。
例如:C -> B -> A,假如现在不想执行C时把A下载进来,那么我们可以用<exclusions>标签
<dependencies>
<dependency>
<groupId>B</groupId>
<artifactId>B</artifactId>
<version>0.0.1</version>
<exclusions>
<exclusion>
<!--被排除的依赖包坐标-->
<groupId>A</groupId>
<artifactId>A</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
我们一般情况下,不要自己去写依赖排除,除非某些特殊情况。
假如:junit依赖的hamcrest-core:1.3有版本问题,我们不想要junit依赖传递进来的hamcrest-core:1.3,那么我们就可以通过exclusion排除掉。
<!-- 引入junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
注意:要跟原来引出依赖的原包处于同一个<dependency></dependency>
范围。
依赖排除hamcrest-core之后,junit肯定就出现了问题,那么我们需要找一个没有版本问题的hamcrest-core再引入进来即可。
<!-- 引入hamcrest-core -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>2.2</version>
</dependency>
依赖排除的场景:
1、项目A依赖项目B,但当项目A不是完全依赖项目B的时候,即项目A只用到了项目B的一部分功能,而正巧项目B这部分功能的实现,并不需要依赖于项目C,这个时候,项目A就应该排除对项目C的依赖。
2、版本不匹配:依赖传递进来的jar包与实际用到的jar有版本不匹配问题,造成项目运行异常。
3、封装公共模块:使用到某个jar中的API,如果此jar中有不需要的传递依赖,可以通过排除依赖传递。
依赖冲突与解决
依赖冲突:一个项目A,通过不同依赖传递路径依赖于X,若在不同路径下传递过来的X版本不同,那么A应该导入哪个版本的X包呢?
由于依赖的内容存在多个版本,如果出现某一个POM依赖多个版本时,则称之为依赖冲突。
依赖冲突遵循两个原则:
1、短路优先(依赖的内容,传递次数越小越优先)。
2、先声明则优先(在POM.xml中,哪个依赖的内容声明dependency靠前,则优先。
举例验证:
引入如下依赖:
<!-- spring核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- spring beans包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- spring环境包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
我们会发现,我们引入了spring-core,但是beans、context以及context里面的aop等都依赖传递了spring-core。那么项目中会引入多个jar吗?
答案是否定的。实际上Maven只引入了一个spring-core。
冲突解决方案:
1、如果依赖路径的长度不同,则采取最短路径原则:
A—>B—>C—>D—>E—>X(version 0.0.2)
A—>F—>X(version 0.0.1)
则A依赖于X(version 0.0.1)。
2、依赖路径长度相同情况下,则采取最先申明原则:
A—>E—>X(version 0.0.1)
A—>F—>X(version 0.0.2)
则在项目A的<depencies></depencies>中,E、F哪个先声明则A依赖哪条路径的X。