解決maven依賴衝突,這篇就夠了!

一、前言

什麼是依賴衝突

依賴衝突是指項目依賴的某一個jar包,有多個不同的版本,因而造成了包版本衝突。

依賴衝突的原因

我們在maven項目的pom中 一般會引用許許多多的dependency。例如,項目A有這樣的依賴關係:

A -> C -> X(1.0)
B -> D -> X(2.0)

X是A的傳遞性依賴,但是兩條依賴路徑上有兩個版本的X,那麼哪個X會被Maven解析使用呢? 兩個版本都被解析顯然是不對的,因為那會造成依賴重複,因此必須選擇一個。

至於怎麼選肯定有它的規則(下面會講),這裡我們先假設最終引用的X(1.0)版本,這樣會不會有問題呢?

當然會有的

1、你想如果B引用X(2.0)的新創建的類,但因為最終被解析的是X(1.0),所以就會出現很典型的NoClassDefFoundErrorClassNotFoundException依賴衝突報錯。

2、如果B引用X(2.0)的新創建的方法,但因為最終被解析的是X(1.0),所以就會拋出 NoSuchMethodError系統異常。

但換種角度,如果最終解析的是X(2.0),就沒問題了嗎?

那也不一定

1、如果X(2.0)刪掉了X(1.0)的一些類,但A已經引用了,同樣也會報NoClassDefFoundError或者ClassNotFoundException錯誤。

2、如果X(2.0)刪掉了X(1.0)的一些方法,但A已經引用了,同樣也會報NoSuchMethodError錯誤。

所以說具體問題還需具體分析,到底採用哪個版本還需要看實際項目。也可能我們需要升級對應的A或者B的版本才能解決問題。

如果有兩個版本的X,Maven是依據什麼原則來選擇解析哪個X呢?

二、maven依賴原則

maven依賴主要有兩大原則

1、路徑最近者優先

相同jar不同版本,根據依賴的路徑長短來決定引入哪個依賴。

舉例

依賴鏈路一:A -> B -> C -> X(1.0)
依賴鏈路二:F -> D -> X(2.0)

該例中X(1.0)的路徑長度為3,而X(2.0)的路徑長度為2,因此X(2.0)會被解析使用。依賴調解第一原則不能解決所有問題,比如這樣的依賴關係:

A -> B -> Y(1.0)
c -> D -> Y(2.0)

Y(1.0)和Y(2.0)的依賴路徑長度是一樣的,都為2。Maven定義了依賴調解的第二原則:

2、第一聲明者優先

在依賴路徑長度相等的前提下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最前的那個依賴優勝。該例中,如果A的依賴聲明在C之前,那麼Y (1.0)就會被解析使用.

注意 子pom內聲明的優先於父pom中的依賴。

三、如何排除依賴

我們先來解釋下什麼是傳遞性依賴

1、什麼是傳遞性依賴

比如當我們項目中,引用了A的依賴,A的依賴通常又會引入B的jar包,B可能還會引入C的jar包。

這樣,當你在pom.xml文件中添加了A的依賴,Maven會自動的幫你把所有相關的依賴都添加進來。

這裡隨便舉一個例子,比如我們為了實現導入導出功能我們可能會引入poi

  <dependency>
     <groupId>org.apache.poi</groupId>
     <artifactId>poi-ooxml</artifactId>
     <version>3.10.1</version>
  </dependency>

當你引入它的時候,它其實還會映入其它jar包

  <dependencies>
    <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi</artifactId>
       <version>3.10.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml-schemas</artifactId>
      <version>3.10.1</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
 </dependencies>

就這樣一層層的,Maven會自動的幫你把所有相關的依賴都添加進來。傳遞性依賴會給項目引入很多依賴,簡化項目依賴管理,但是也會帶來問題。

最明顯的就是容易發生依賴衝突。

2、如何排除依賴

關鍵字:exclusions

exclusions可以包含一個或者多exclusion子元素,因此可以排除一個或者多個傳遞性依賴。

需要注意的是,聲明exclusion的時候只需要groupldartifactld,而不需要要version元素。

示例

   <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.10.1</version>
            <!--排除poi依賴-->
        <exclusions>
            <exclusion>
                <artifactId>poi</artifactId>
                <groupId>org.apache.poi</groupId>
             </exclusion>
        </exclusions>
   </dependency>

四、實例演示如何解決依賴衝突

我們先來複現因為依賴衝突導致項目報錯的。

1、製造依賴衝突

在當前項目中,我即引用了org.apache.poi,又引用了com.alibaba的easyexcel。

pom文件如下:

   <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.10.1</version>
   </dependency>
   <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>2.2.8</version>
   </dependency>

然後在項目中添加如下程式碼:

@SpringBootApplication
public class MainApplication {

	public static void main(String[] args) {
		SpringApplication.run(MainApplication.class, args);
		test();

	}
	public static void test() {
		//1.獲取文件地址
		String fileName = "/excel/test.xlsx";
		//2、調用easyExcel裡面的方法實現寫操作
		EasyExcel.write(fileName, UserDto.class).sheet("某某報表").doWrite(new ArrayList<>());
	}
}	

我們發現在SpringBoot啟動的時候就報NoClassDefFoundError異常了。

這就是依賴衝突導致的異常報錯。

為什麼會這樣呢,我們可以通過idea看下,打開maven菜單,點擊show dependencies

看到這裡你大概就已經明白,因為我當前的easyexcel引入的poi是3.17版本的,但是因為根據maven依賴原則,實際引入的poi版本確實是3.10.1版本的。

而DefaultTempFileCreationStrategy這個類在3.10.1這個版本並沒有,只有3.17版本才有,所以報了這個錯誤,這麼一解釋是不是都通了。

然後我們再來思考一個問題,上面這個案例我們一眼就知道是最終應用哪個依賴里的哪個版本,但如果你的項目中依賴許許多多的jar,肉眼排查就沒那麼方便了,這裡推薦一個

Maven管理插件

2、Maven Helper插件分析jar包衝突

選擇並下載插件

在pom文件中看到 dependency analyzer標誌,說明maven helper插件就安裝成功了。

點擊dependency analyzer之後就會進入到下面的頁面

從圖中可以看出有哪些jar存在衝突,存在衝突的情況下最終採用了哪個依賴的版本。標紅的就是衝突版本,白色的是當前的解析版本

如果我們想保留標紅的版本,那我們可以標白區域右擊選擇排除(Exclude)

然後我們再來看pom文件,發現在org.apache.poi中已經移除了poi了。

   <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi-ooxml</artifactId>
       <version>3.10.1</version>
       <exclusions>
          <exclusion>
             <artifactId>poi</artifactId>
             <groupId>org.apache.poi</groupId>
           </exclusion>
       </exclusions>
    </dependency>
    <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>easyexcel</artifactId>
       <version>2.2.6</version>
    </dependency>

我們再來看最終引用的poi版本是哪個?

我們發現最終的版本最終已經變成3.17版本,現在再啟動就不會報錯了,成功解決依賴衝突問題。

五、總結

一般我們在解決依賴衝突的時候,都會選擇保留jar高的版本,因為大部分jar在升級的時候都會做到向下兼容,所以只要保留高的版本就不會有什麼問題。

但是有些包,版本變化大沒法去做向下兼容,高版本刪了低版本的某些類或者某些方法,那麼這個時候就不能一股腦的去選擇高版本,但也不能選擇低版本。

就比如下面這個案例

依賴鏈路一:A -> B -> C -> X(1.0)
依賴鏈路二:F -> D -> X(2.0)

X(2.0) 沒有對 X(1.0) 做向下兼容也就是說可能存在排除哪個都不行,那怎麼辦我們只能考慮升級A的版本或者降低F的版本。比如A升級到A(2.0),使它依賴的X版本

變成X(2.0)這樣的話就解決依賴衝突。

但話有說回來 A升級到A(2.0) 可能會影響許許多多的地方,比如自己項目中程式碼是否需要改變,或者因為 A升級到A(2.0) 導致 B和C的版本有所改變,這些影響點都需要我

們去考慮的。所以說為什麼說一個大型項目穩定後,pom文件的升級是件繁瑣的事情,那是因為考慮的東西是在太多了,稍有不慎就會因為依賴衝突而導致系統報錯。

聲明: 公眾號如需轉載該篇文章,發表文章的頭部一定要 告知是轉至公眾號: 後端元宇宙。同時也可以問本人要markdown原稿和原圖片。其它情況一律禁止轉載!