Maven依賴衝突解決總結

香吧香發表於2023-02-17

轉載請註明出處:

1.Jar包衝突的通常表現

Jar包衝突往往是很詭異的事情,也很難排查,但也會有一些共性的表現。

  • 丟擲java.lang.ClassNotFoundException:典型異常,主要是依賴中沒有該類。導致原因有兩方面:第一,的確沒有引入該類;第二,由於Jar包衝突,Maven仲裁機制選擇了錯誤的版本,導致載入的Jar包中沒有該類。

  • 丟擲java.lang.NoSuchMethodError:找不到特定的方法。Jar包衝突,導致選擇了錯誤的依賴版本,該依賴版本中的類對不存在該方法,或該方法已經被升級。

  • 丟擲java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因同上。

  • 沒有異常但預期結果不同:載入了錯誤的版本,不同的版本底層實現不同,導致預期結果不一致。

2.Jar包衝突的本質

Jar包衝突的本質:Java應用程式因某種因素,載入不到正確的類而導致其行為跟預期不一致

具體分兩種情況:

  • 情況一:專案依賴了同一Jar包的多個版本,並且選錯了版本;

  • 情況二:同樣的類在不同的Jar包中出現,導致JVM載入了錯誤的類;

  情況一,同一個依賴引入了多個Jar包版本,不同的Jar包版本有不同的類和方法。由於Maven依賴樹的仲裁機制導致Maven載入了錯誤的Jar包,從而導致Jar包衝突;

  情況二,同一類在不同的Jar包中出現。這種情況是由於JVM的同一個類載入器對於同一個類只會載入一次,現在載入一個類之後,同全限定名的類便不會進行載入,從而出現Jar包衝突的問題。

  針對第二種情況,如果不是類衝突丟擲了異常,你可能根本意識不到,所以就顯得更為棘手。這種情況就可以採用前文所述的透過分析不同類載入器的優先順序及載入路徑、檔案系統的檔案載入順序等進行調整來解決。

3.解決Jar包衝突的方法

  幾種場景下解決Jar衝突的方法:

    • Maven預設處理:路徑最近者優先和第一宣告優先;

    • 排除法:使用 Maven Helper,可以將衝突的Jar包在pom.xml中透過exclude來進行排除;

    • 版本鎖定法:如果專案中依賴同一Jar包的很多版本,一個個排除非常麻煩,此時可用版本鎖定法,即直接明確引入指定版本的依賴。此種方式的優先順序最高。

    • 透過設定classpath指定jar包載入的先後順序 

3.1Mavenhelper 解決衝突

  

  具體步驟:  

    1.使用mavenHelper 選擇 Confilicts;

    2.如果列表存在衝突的依賴,則點選檢視衝突的詳情

    3.對沖突的依賴可進行右鍵;使用 exclude 進行排除即可解決衝突

3.2 透過設定 classpath 指定jar包載入的先後順序

  第一種方式:
java -jar -classpath C:\dependency\framework.jar:C:\location\otherFramework.jar  test.jar

  第二種方式(-cp 等於 -classpath):

java -jar -cp C:\dependency\framework.jar:C:\location\otherFramework.jar  test.jar

  第三種方式:

-Djava.class.path=a.jar

  第四種方式:

-Xbootclasspath/a: 

  classpath 理解和設定可以檢視這篇文章Tomcat 與 JVM 中classpath的理解和設定總結 

4.Maven管理機制

依賴傳遞原則

  當在Maven專案中引入A的依賴,A的依賴通常又會引入B的jar包,B可能還會引入C的jar包。這樣,當你在pom.xml檔案中新增了A的依賴,Maven會自動的幫你把所有相關的依賴都新增進來。

  如下圖所示,專案 A 依賴於專案 B,B 又依賴於專案 C,此時 B 是 A 的直接依賴,C 是 A 的間接依賴。

 

  Maven 的依賴傳遞機制是指:不管 Maven 專案存在多少間接依賴,POM 中都只需要定義其直接依賴,不必定義任何間接依賴,Maven 會動讀取當前專案各個直接依賴的 POM,將那些必要的間接依賴以傳遞性依賴的形式引入到當前專案中。Maven 的依賴傳遞機制能夠幫助使用者一定程度上簡化 POM 的配置。

  基於 A、B、C 三者的依賴關係,根據 Maven 的依賴傳遞機制,我們只需要在專案 A 的 POM 中定義其直接依賴 B,在專案 B 的 POM 中定義其直接依賴 C,Maven 會解析 A 的直接依賴 B的 POM ,將間接依賴 C 以傳遞性依賴的形式引入到專案 A 中。

   即當一個依賴需要另外一個依賴支撐時,Maven會幫我們把相應的依賴依次新增到專案當中。這樣的好處是,使用起來就非常方便,不用自己挨個去找依賴Jar包了。壞處是會引起Jar包衝突,

最短路徑優先原則

  但當一個間接依賴存在多條引入路徑時,為了避免出現依賴重複的問題,Maven 透過依賴調節來確定間接依賴的引入路徑。

  依賴調節遵循以下兩條原則:

    1. 引入路徑短者優先

    2. 先宣告者優先

  以上兩條原則,優先使用第一條原則解決,第一條原則無法解決,再使用第二條原則解決。

引入路徑短者優先

  引入路徑短者優先,顧名思義,當一個間接依賴存在多條引入路徑時,引入路徑短的會被解析使用。

  例如,A 存在這樣的依賴關係: A->B->C->D(1.0) A->X->D(2.0)

  D 是 A 的間接依賴,但兩條引入路徑上有兩個不同的版本,很顯然不能同時引入,否則造成重複依賴的問題。根據 Maven 依賴調節的第一個原則:引入路徑短者優先,D(1.0)的路徑長度為 3,D(2.0)的路徑長度為 2,因此間接依賴 D(2.0)將從 A->X->D(2.0) 路徑引入到 A 中。

先宣告者優先

  先宣告者優先,顧名思義,在引入路徑長度相同的前提下,POM 檔案中依賴宣告的順序決定了間接依賴會不會被解析使用,順序靠前的優先使用。

  例如,A 存在以下依賴關係: A->B->D(1.0) A->X->D(2.0)

  D 是 A 的間接依賴,其兩條引入路徑的長度都是 2,此時 Maven 依賴調節的第一原則已經無法解決,需要使用第二原則:先宣告者優先。

5.Tomcat啟動時Jar包和類的載入順序

最後,梳理一下Tomcat啟動時,對Jar包和類的載入順序,其中包含上面提到的不同種類的類載入器預設載入的目錄:

  • $java_home/lib 目錄下的java核心api;
  • $java_home/lib/ext 目錄下的java擴充套件jar包;
  • java -classpath/-Djava.class.path所指的目錄下的類與jar包;
  • $CATALINA_HOME/common目錄下按照資料夾的順序從上往下依次載入;
  • $CATALINA_HOME/server目錄下按照資料夾的順序從上往下依次載入;
  • $CATALINA_BASE/shared目錄下按照資料夾的順序從上往下依次載入;
  • 專案路徑/WEB-INF/classes下的class檔案;
  • 專案路徑/WEB-INF/lib下的jar檔案;

上述目錄中,同一資料夾下的Jar包,按照順序從上到下一次載入。如果一個class檔案已經被載入到JVM中,後面相同的class檔案就不會被載入了

 
 
 

相關文章