代码改变世界

Maven解读:强大的依赖体系

2015-03-18 20:02  让猪再飞会  阅读(3604)  评论(2编辑  收藏  举报

Github地址:https://github.com/zwjlpeng/Maven_Detail

Maven最大的好处就是能够很方便的管理项目对第三方Jar包的依赖,只需在Pom文件中添加几行配置文件,就可以将第三方的Jar包纳入自已项目的类路径下,在Pom配置文件中我们也可以指定第三方Jar包的版本号,表明依赖第三方某一版本的Jar包,因此通过Maven管理项目的依赖,我们可以很容易的对项目依赖的第三方Jar包进行升级,升级过程中需要我们做的仅仅是更改配置文件->重新mvn package即可,是不是so easy~,想想通过传统方式搭建的Web工程,当我们为了解决低版本Spring中的BUG如版本3.2.3中,当我们设置了@RequestParam(value="username", required=false)注解后,传入参数为空时,系统却抛出异常~】时,不得不升级Spring的版本时,我们需要怎么做?需要到Spring官网下载高版本,然后将相应的Jar包添加到工程的Build Path下,重新编译工程,当一个Jar包需要升级时,估且可以忍受,但是当多了,我想大概也只能呵呵了...

当很多人从一个软件迁移到另一个软件并不再回头的时候,就值得我们注意了...

直接依赖/间接依赖

Maven中最容易理解的就是直接依赖,A项目的运行需要有B项目的存在,这就是一个直接依赖,当B项目的运行需要有C项目的存在时,这里就存在A项目对C项目的一个间接依赖,A项目要想成功运行,在其类路径下必须要存在B项目和C项目,当然使用Maven我们不需要关注这种间接依赖,Maven会帮我们处理,如我们的项目中需要spring-core,在Pom文件中我们只需要添加如下配置,再看看我们的类路径是不是存在了spring-core-4.1.4.RELEASE.jar/commons-logging-1.2.jar两个Jar包,其中这个commons-logging-1.2.jar包就是通过间接依赖加入类路径下的~

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>

再看看spring-core-4.1.4.RELEASE.jar里面的Pom文件,在这个Pom文件中将对commons-logging-1.2.jar依赖设置为Compile并且非可选,下是是其Pom.xml文件

<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.10</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>net.sf.jopt-simple</groupId>
  <artifactId>jopt-simple</artifactId>
  <version>4.8</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.4</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>

在这个Pom.xml文件中,只有这个Jar包是必须的,其他依赖均是可选的(Optional)依赖,在Maven中可选依赖是不会进行传递的,为什么要有可选依赖呢?例如,一个持久层模块不但可以持久化Oracle数据库,也可以持久化Mysql数据库,那么在这个持化层框架中应该对应了两份代码,一份是关于Oracle持久化的处理,一份是关于Mysql持久化的处理,如果将持久层模块的OracleMysql驱动均设置为非可选依赖,那么依赖这个持久层框架的项目类路径中将同时出现Mysql以及Oracle的驱动Jar包,如果真的这样,你会不会感觉到这种设计很奇葩~,因此MavenOptional关键字就诞生了~~~

重复引入的处理?

在什么情况下,会出现依赖的重复引入呢?

场景一:项目A依赖于项目B与项目C,项目B与项目C均依赖于项目D,这时项目中就会出现两份D项目(有可能B项目与C项目依赖的D项目的版本还不一样~),这种情况下,Maven是如何处理的呢?

规则,第一声明者优化

这种情况下,Maven处理方法,是完全依赖于项目B与项目C在Pom.xml文件中声明的顺序,哪个声明在前就使用哪个项目的D依赖,如下是一个典型的例子~

<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-common</artifactId>
  <version>0.99.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
</depend

上面Pom配置文件中最终依赖的commons-logging为commons-logging-1.1.3.jar,但是当我们调整配置文件依赖的顺序,变成如下配置时,commons-logging的版本又变为了commons-logging-1.2.jar,结论是完全符合第一声明者优化的原则

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.apache.hbase</groupId>
  <artifactId>hbase-common</artifactId>
  <version>0.99.0</version>
</dependency>

场景二:一个Pom文件中声明了对一个项目的高低版本的依赖

规则,使用最后声明者,示例如下

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <!-- 版本一 -->
    <version>2.0.6</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <!-- 版本二 -->
    <version>4.1.4.RELEASE</version>
</dependency>

最终spring-core-4.1.4.RELEASE.jar的版本号为4.1.4,但当将版本一与版本二的顺序调换后,则项目类路径下依赖的Jar包就会变成2.0.6

场景三:A项目依赖于B项目,B项目依赖于D项目,A项目依赖于C项目,C项目依赖于E项目,E项目又依赖于D项目,简化一下,即A->B->D/A->C->E->D,这时项目中也会由于传递依赖,引入了对D项目的两次依赖

规则:最短路径优先

在场景三中,由于A项目从B项目得到对D项目的依赖路径要比从C项目中获取D项目依赖的路径要短,说起来太转口了~,因此会优先采用从B项目中得到的D依赖,如下是一个示例~

这个在开源项目中,找了半天也没有找到很好的例子

Maven中的依赖范围

什么是依赖范围,依赖范围指明了第三方Jar包在类路径中的可见性,当我们向项目中添加了依赖,默认的依赖范围是Compile

Maven中具体的几种编译范围如下

compile:默认的编译范围,表示该Jar包在编译、运行、测试类路径中均可见

test:表示该Jar包仅在测试类路径中可见,正式发布打包时,里面没有该Jar包

provided:表示该Jar包在运行时由服务器提供,该Jar包对于编译和测试classpath有效,但在运行时无效,即发布时,最终打包的项目中不会含有该Jar包。典型范例:servlet-api 

runtime:运行时依赖范围,对于测试和运行classpath有效,但在对编译主代码时无效。典型范例:JDBC

system:系统依赖范围,使用system范围的依赖必须通过systemPath元素显示地指定依赖文件的路径,不依赖Maven仓库解析,但是这样会对项目的移植性造成严重的影响,如下是使用系统依赖的一个典型例子

<dependency>
    <artifactId>com.netease</artifactId>
    <groupId>httpclient</groupId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>E:\source\Crawer\lib\commons-httpclient-3.1_2.jar</systemPath>
</dependency

在上面配置文件中,真正有作用的一句就是systemPath,其他的可以随意配置,没什么影响~,经过上面的配置后,可以发现项目的classpath中已经出现了commons-httpclient-3.1_2.jar包

作用system指定依赖范围,该依赖范围会出现在编译、测试、运行时的类路径下

import:这个范围maven 2.0.9以上才支持,据说是为了支持继承,没用过~~~

传递性依赖的依赖范围处理

传递性会导致依赖范围的变更,如A项目对B项目的依赖范围是compile,B项目对C项目的依赖范围是provided,那么最终A项目对C项目的依赖范围是啥子呢?以下是Maven官方给的一个表格

  compile provided runtime test
compile compile  runtime
provided provided provided 
runtime  runtime  runtime
test test   test

 

 

 

 

 

 

在这个表格中第一列代表的是一级依赖,第一行代表的是二级依赖,如表格中的红包部分,A项目对B项目的依赖范围是provided,B项目对C项目的依赖是compile,那么最终A项目对C项目的依赖是provided,在表格中-代表的是在传递性依赖中该依赖会被忽略

好了现在可以回答刚才提的问题,A项目对B项目是compile,B项目对C项目是provided那么最终的结果是A项目不对C项目进行依赖~

解答,A项目对B项目有依赖,说明B项目必须要在A项目的编译路径下,B项目对C项目是provided说明该依赖是由容器来提供的,在正式的发布包中B项目是不包含C项目的Jar包,因此A项目对B项目依赖后,会认为C项目是由容器提供,因此会忽略该依赖,默认认为容器中有,此时也不会报错,因为B项目已经编译打包了~