1.4.4.传递性依赖
一个传递性依赖就是一个依赖的依赖。如果project-a依赖于project-b,而后者接着依赖于project-c,那么project-c就是被认为是project-a的传递性依赖。如果project-c依赖于project-d,那么project-d也被认为是project-a的传递性依赖。Maven的优势之一就是它能够管理传递性依赖,并且能够帮助开发者屏蔽掉跟踪所有编译期和运行期依赖细节。有了传递性依赖机制。在使用Spring Framework 的时候就不用去考虑它依赖了那些包,也不用担心一如多余的依赖。Maven会解析各个直接依赖的POM,将那些必须的间接依赖,以传递性依赖的形式引入到当前的项目中。
Maven建立一个依赖图,并且处理一些可能发生的冲突和重叠。
§传递性依赖的范围
依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响如下表
如果project-a包含一个project-b的测试范围依赖,后者包含一个对于project-c的编译范围依赖。project-c将会是project-a的测试范围传统依赖。
从表中发现这样的规律:当第二个直接依赖范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;当第二个直接依赖范围是test的时候,传递性依赖不会得以传递;当第二个直接依赖范围是provided的时候,只传递第一直接依赖范围为provided的依赖。且传递性依赖的范围同样为provided;当第二直接依赖范围是runtime的时候,依赖性传递的范围与第一直接依赖的范围一致,但compile列外,此时传递性依赖的范围为runtime。
1.4.5.依赖版本界限
不是必须为依赖声明摸个特定的版本,可以指定一个满足需求的依赖版本界限。例如:项目依赖JUnit3.8或以上的版本,或者说依赖于JUnit1.2.10和1.2.14之间的某个版本。可以使用如下的字符来围绕一个或者多个版本号,开实现版本界限(,)不包含量词,[, ]包含量词。例如想访问JUnit任意大于等于3.8但小于4.0版本的
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>[3.8,4.0)</version>
<scope>test</scope>
</dependency>
如果想要依赖任意不大于3.8.1的版本,可以只指定一个上包含边界。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>[,3.8.1]</version>ex-de
<scope>test</scope>
</dependency>
在逗号前面或者后面的版本不是必须的,这种空缺意味着正无穷或者负无穷。
注意:当生命一个“正常的”版本如JUnit3.8.2,最好表述成“允许任何版本,最好是3.8.2”。当检测到版本冲突的时候,Maven会使用冲突算法来选择最好的版本。如果指定[3.8.2],它意味只有3.8.2会被使用,没有其它地方依赖地使用了一个版本[3.8.1],就会得到一个构建失败的报告,说明有版本冲突。要保守党的使用它,只有在确实需要的时候才使用。正好的做法是通过dependencyManagement来解决冲突。
1.4.6.依赖调解
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)会被解析使用。
依赖调解第一原则不能解决所有问题,例如依赖关系A –> B -> Y(1.0),A -> B ->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度都是2。在Maven2.0.8及之前的版本中,这是不确定的,但是从Maven2.0.9开始,为了尽可能避免构建的不确定,Maven定义了依赖调解的第二原则:第一原则优先,在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会解析使用,顺序最靠前的那个依赖被使用。如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。
1.4.7.可选依赖
假设有这样一个依赖关系,项目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项目时需要两个依赖,如下:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project-b</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.4.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>swarmcache</groupId>
<artifactId>swarmcache</artifactId>
<version>1.0RC2</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.13</version>
</dependency>
</dependencies>
</project>
上述XML代码片段中,使用<optional>元素表示net.sf.ehcache和swarmcache这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其它项目依赖于B的时候,这两个依赖不会被传递。因此,当A依赖于项目B的时候,那么在项目A中就要显示的声明使用哪种依赖项。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-application</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>swarmcache</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
</project>
注意:在理想的情况下,是不应该使用可选依赖的。使用可选依赖的原因是某一个项目实现了多个特征,在面向对象设计中,有一个单一责任原则,意指一个类应该只有一项职责,而不是糅合太多的功能,这个原则在规划Maven项目的时候也同样适用。
1.4.8.排除依赖
有很多时候需要排除一个传递性依赖,就要适用exclusions元素声明排除依赖。
<dependency>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>project-b</artifactId>
</exclusion>
</exclusions>
</dependency>
exclusions可以包含一个或者多个exclusion子元素。
排除并且替换一个传递性依赖。
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.5.ga</version>
<exclusions>
<exclusion>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jta_1.1_spec</artifactId>
<version>1.1</version>
</dependency>
</dependencies>
没有标记说依赖geronimo-jta_1.1_spec是一个替换,它只是正好提供了原来JTA依赖一样的API。有以下几种情况可能想要排除或者替换传递性依赖的情况:
§构建的groupId和artifactId已经改变,而当前项目需要一个传递性依赖不同名称的版本—结果是classpath中会出现同样项目的两份内容。一般来说Maven会捕获到这种冲突并且使用该项目的一个单独的版本,但是当artifactId和gruopId不一样的时候,Maven就会认为他们是两种不同的类库。
§某个构件没有在项目中被使用,而且该传递性依赖没有被标示为可选依赖的这种情况下,可能要排除这样依赖,因为它不是系统需要的东西,尽量减少应用程序发布时的类库数目。
§一个构件已经在运行时的容器中提供了,因此不应该被包含在构件中。
§为了排除一个可能是多个实现的API依赖。
1.4.9.归类依赖
有很多关于Spring Framework的依赖,它们分别是spring-aop,spring-beans,spring-context,spring-context-support,spring-core等,它们是来自同一项目的不同模块,因此,是由于这些依赖的版本都是相同的。如果将要升级Spring Framework,这些依赖的版本会一起升级。所以应该在一个唯一的地方定义一个版本,并且在dependency声明中引用这一版本,这样在升级Spring Framework的时候就只需要修改一处。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<springframework.version>3.0.0.RELEASE</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
</project>
1.4.10.依赖管理
Maven提供dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证自模块依赖的使用灵活性。
实际的项目中,你会有许多的Maven模块,而且你往往发现这些模块有很多依赖完全相同的构建,A模块有个对spring的依赖,B模块也有,它们的依赖配置一模一样,同样的groupId, artifactId, version,或者还有exclusions,classifer。这是一种重复,重复就意味着潜在的问题,Maven提供的dependencyManagement就是用来消除这种重复的。
正确的做法是:
1. 在父模块中使用dependencyManagement配置依赖
2. 在子模块中使用dependencies添加依赖
dependencyManagement实际上不会真正引入任何依赖,dependencies才会。但是,当父模块中配置了某个依赖之后,子模块只需使用简单groupId和artifactId就能自动继承相应的父模块依赖配置。
父模块中如此声明:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.2</version>
</dependency>
...
<dependencies>
</dependencyManagement>
</project>
子模块中如此声明:
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.sonatype.mavenbook</groupId>
<artifactId>a-parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>project-a</artifactId>
...
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
依赖配置越复杂,依赖管理所起到的作用就越大,它不仅能够帮助简化配置,它还能够巩固依赖配置,也就是说,在整个项目中,对于某个构件(如mysql)的依赖配置只有一种,这样就能避免引入不同版本的依赖,避免依赖冲突。
1.4.11.优化依赖
§mvn dependency:list 此命令查看当前项目的已解析的依赖。
§mvndependency:tree 此命令查看当前项目的依赖树。
§mvndependency:analyze 此命令可以帮助分析当前项目的依赖。使用但未声明的依赖与声明但未使用的依赖。
使用以上命令可以帮助我们了解项目所有依赖的具体情况。