1. 程式人生 > >Maven的傳遞性依賴及其jar包衝突解決

Maven的傳遞性依賴及其jar包衝突解決

一、Maven簡介

Maven是一個跨平臺的專案管理工具。作為Apache組織的一個頗為成功的開源專案,其主要服務於基於Java平臺的專案建立,依賴管理和專案資訊管理。

二、Maven的依賴管理

1、依賴配置

基本配置:

<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:用來排除傳遞性依賴

大部分依賴宣告只包含基本座標

2、依賴範圍

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元素可以引用環境變數。

3、傳遞性依賴

傳遞性依賴是在maven2中新增的新特徵,這個特徵的作用就是你不需要考慮你依賴的庫檔案所需要依賴的庫檔案,能夠將依賴模組的依賴自動的引入。例如我們依賴於spring的庫檔案,但是spring本身也有依賴,如果沒有傳遞性依賴那就需要我們瞭解spring專案依賴,自己新增到我們的專案中。有了傳遞性依賴機制,在使用Spring Framework的時候就不用去考慮它依賴了什麼,也不用擔心引入多餘的依賴。Maven會解析各個直接依賴的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>[email protected]</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是傳遞性依賴。第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍。

最左邊一行表示第一直接依賴範圍,最上面一行表示第二直接依賴範圍,中間的交叉單元格則表示傳遞性依賴範圍。
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。

4、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版本

        <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>

2)在ide中右擊進行處理,處理完後在pom.xml中也會新增exclusion元素