Maven的依赖管理

依赖配置

<project> 
 ...
  <dependencies>  
    <dependency>  
      <groupId>...</groupId>  
      <artifactId>...</artifactId>  
      <version>...</version>
      <type>...</type>
      <scope>...</scope>
      <optional>...</optional>
      <exclusions>  
        <exclusion>  
          <groupId>...</groupId>  
          <artifactId>...</artifactId>  
        </exclusion>
         ...
      </exclusions>  
    </dependency> 
     ...
  </dependencies> 
  ... 
</project>

根元素下project下的dependencies可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖可以包含的元素有:

  • groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven根据坐标才能找到需要的依赖。
  • type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值是jar。
  • scope:依赖的范围
  • optional:标记依赖是否可选。
  • exclusions:用来排除传递性依赖
  • 大部分依赖声明只包含基本坐标

依赖范围

Maven在编译主代码的时候需要使用一套classpath,在编译和执行测试的时候会使用另一套classpath,实际运行项目的时候,又会使用一套classpath。

依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:

  • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译,测试和运行的时候都需要使用该依赖。
  • provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
  • test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子就是JUnit,它只有在编译测试代码及运行测试的时候才需要。
  • runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
  • system:系统依赖范围。该依赖范围与provided所表示的依赖范围一致,对于编译和测试classpath有效,但在运行时无效。只是使用system范围依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用,systemPath元素可以引用环境变量。

传递性依赖

传递性依赖是在maven2中添加的新特征,这个特征的作用就是你不需要考虑你依赖的库文件所需要依赖的库文件,能够将依赖模块的依赖自动的引入。例如我们依赖于spring的库文件,但是spring本身也有依赖,如果没有传递性依赖那就需要我们了解spring项目依赖,自己添加到我们的项目中。有了传递性依赖机制,在使用Spring Framework的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

例如:项目中用到spring-core,而它又依赖commons-logging,从其构件POM http://repo1.maven.org/maven2/org/springframework/spring-core/4.3.2.RELEASE/spring-core-4.3.2.RELEASE.pom中可以看出

<?xml version="1.0" encoding="UTF-8"?>
 
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
<modelVersion>4.0.0</modelVersion>
 
<groupId>org.springframework</groupId>
 
<artifactId>spring-core</artifactId>
 
<version>4.3.2.RELEASE</version>
 
<name>Spring Core</name>
 
<description>Spring Core</description>
 
<url>https://github.com/spring-projects/spring-framework</url>
 
 
-<organization>
 
<name>Spring IO</name>
 
<url>http://projects.spring.io/spring-framework</url>
 
</organization>
 
 
-<licenses>
 
 
-<license>
 
<name>The Apache Software License, Version 2.0</name>
 
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
 
<distribution>repo</distribution>
 
</license>
 
</licenses>
 
 
-<developers>
 
 
-<developer>
 
<id>jhoeller</id>
 
<name>Juergen Hoeller</name>
 
<email>jhoeller@pivotal.io</email>
 
</developer>
 
</developers>
 
 
-<scm>
 
<connection>scm:git:git://github.com/spring-projects/spring-framework</connection>
 
<developerConnection>scm:git:git://github.com/spring-projects/spring-framework</developerConnection>
 
<url>https://github.com/spring-projects/spring-framework</url>
 
</scm>
 
 
-<issueManagement>
 
<system>Jira</system>
 
<url>https://jira.springsource.org/browse/SPR</url>
 
</issueManagement>
 
 
-<dependencies>
 
 
-<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>5.0.2</version>
 
<scope>compile</scope>
 
<optional>true</optional>
 
</dependency>
 
 
-<dependency>
 
<groupId>org.aspectj</groupId>
 
<artifactId>aspectjweaver</artifactId>
 
<version>1.8.9</version>
 
<scope>compile</scope>
 
<optional>true</optional>
 
</dependency>
 
</dependencies>
 
</project>

可以看出还依赖了其他构建。Maven就是根据此POM文件获得它的依赖的,从而实现传递性依赖。

 假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。

假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。

最左边一行表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

 

compile

test

provided

runtime

compile

compile

---

---

runtime

test

test

---

---

test

provided

provided

---

provided

provided

runtime

runtime

---

---

runtime

 

  • 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
  • 当第二直接依赖的范围是test的时候,依赖不会得以传递;
  • 当第二直接依赖的范围是provided的时候,只传递第一直接依赖的范围也为provided的依赖,切传递性依赖的范围同样为provided;
  • 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。

maven对传递性依赖的处理

有些依赖,maven会对其按照下述原理自动处理

1).短路优先:谁离得最近就使用谁的依赖jar包

C到达A为C->B->A

C到达B为C->B

例如:

A中的 commons-io的版本为2.4

B中的commons-io的版本为2.0

C中依赖于B,B依赖于A

则C的junit的包为2.0版本

因为依赖的短路优先

2).如果两条路都是一样长的时候呢?

C到达A为C->A

C到达B为C->B

则看pom文件中依赖的两个工程谁在前面就是用哪个版本

例如:

这里使用的common-io为2.4版本也就是A的jar包

<dependency>
            <groupId>org.lonecloud.A</groupId>
            <artifactId>A</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--C依赖于B但是会将A的依赖传递进来 -->
        <dependency>
            <groupId>org.lonecloud.B</groupId>
            <artifactId>B</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <!-- 此标签的作用是可以将B的传递依赖关系A不传递给C -->
            <!-- <exclusions> <exclusion> <groupId>org.lonecloud.A</groupId> <artifactId>A</artifactId> 
                </exclusion> </exclusions> -->
        </dependency>

C文件中添加了A和B的依赖项的时候谁最先加载则使用谁的jar包

下面使用的是2.0的版本,也就是B中的jar包

<dependency>
            <groupId>org.lonecloud.B</groupId>
            <artifactId>B</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <!-- 此标签的作用是可以将B的传递依赖关系A不传递给C -->
            <!-- <exclusions> <exclusion> <groupId>org.lonecloud.A</groupId> <artifactId>A</artifactId> 
                </exclusion> </exclusions> -->
        </dependency>
        <dependency>
            <groupId>org.lonecloud.A</groupId>
            <artifactId>A</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

Maven依赖jar包冲突解决

1、判断jar是否正确的被引用

1)、在项目启动时加上VM参数:-verbose:class

项目启动的时候会把所有加载的jar都打印出来 类似如下的信息:

classpath加载的jar

具体load的类

我们可以通过上面的信息查找对应的jar是否正确的被依赖,具体类加载情况,同时可以看到版本号,确定是否由于依赖冲突造成的jar引用不正确;

2)、 通过maven自带的工具:mvn dependency:tree

具体后面可以加 -Dverbose 参数 ,详细参数可以去自己搜,这里不详细介绍。

比如分析如下POM

运行: mvn dependency:tree -Dverbose

输出结果:

通过里面的信息可以看到 两个jar都commons-logging存在依赖,但是版本不同。里面的详细信息显示引用了 commons-logging:commons-logging:jar:1.1 去掉了commons-logging:commons-logging:jar:1.0.3 (omitted for duplicate)。

3)、在Myeclipse或者idea或者eclipse中用pom编辑器打开一个pom文件,在Dependency Hierarchy的Tab页中,就可以查看当前pom文件中显示声明的jar包,及这些显示声明的jar中隐式引入的依赖jar包。

这样就可以查看有哪些隐式的依赖jar会导致jar包冲突了。

通过以上方法我们可以看到项目中引用jar版本号;接下来就是如何排除掉我们不想要版本的jar;

2、冲突的解决

1)在pom.xml中引用的包中加入exclusion,排除依赖

<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>dubbo</artifactId>
			<version>2.5.3</version>
			<exclusions>
				<exclusion>
					<artifactId>spring</artifactId>
					<groupId>org.springframework</groupId>
				</exclusion>
			</exclusions>
		</dependency>

去除全部依赖

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
</dependency>