轉載請註明出處:
Jar包衝突往往是很詭異的事情,也很難排查,但也會有一些共性的表現。
丟擲java.lang.ClassNotFoundException:典型異常,主要是依賴中沒有該類。導致原因有兩方面:第一,的確沒有引入該類;第二,由於Jar包衝突,Maven仲裁機制選擇了錯誤的版本,導致載入的Jar包中沒有該類。
丟擲java.lang.NoSuchMethodError:找不到特定的方法。Jar包衝突,導致選擇了錯誤的依賴版本,該依賴版本中的類對不存在該方法,或該方法已經被升級。
丟擲java.lang.NoClassDefFoundError,java.lang.LinkageError等,原因同上。
沒有異常但預期結果不同:載入了錯誤的版本,不同的版本底層實現不同,導致預期結果不一致。
Jar包衝突的本質:Java應用程式因某種因素,載入不到正確的類而導致其行為跟預期不一致。
具體分兩種情況:
情況一:專案依賴了同一Jar包的多個版本,並且選錯了版本;
情況二:同樣的類在不同的Jar包中出現,導致JVM載入了錯誤的類;
情況一,同一個依賴引入了多個Jar包版本,不同的Jar包版本有不同的類和方法。由於Maven依賴樹的仲裁機制導致Maven載入了錯誤的Jar包,從而導致Jar包衝突;
情況二,同一類在不同的Jar包中出現。這種情況是由於JVM的同一個類載入器對於同一個類只會載入一次,現在載入一個類之後,同全限定名的類便不會進行載入,從而出現Jar包衝突的問題。
針對第二種情況,如果不是類衝突丟擲了異常,你可能根本意識不到,所以就顯得更為棘手。這種情況就可以採用前文所述的通過分析不同類載入器的優先順序及載入路徑、檔案系統的檔案載入順序等進行調整來解決。
幾種場景下解決Jar衝突的方法:
Maven預設處理:路徑最近者優先和第一宣告優先;
排除法:使用 Maven Helper,可以將衝突的Jar包在pom.xml中通過exclude
來進行排除;
版本鎖定法:如果專案中依賴同一Jar包的很多版本,一個個排除非常麻煩,此時可用版本鎖定法,即直接明確引入指定版本的依賴。此種方式的優先順序最高。
基於 A、B、C 三者的依賴關係,根據 Maven 的依賴傳遞機制,我們只需要在專案 A 的 POM 中定義其直接依賴 B,在專案 B 的 POM 中定義其直接依賴 C,Maven 會解析 A 的直接依賴 B的 POM ,將間接依賴 C 以傳遞性依賴的形式引入到專案 A 中。
即當一個依賴需要另外一個依賴支撐時,Maven會幫我們把相應的依賴依次新增到專案當中。這樣的好處是,使用起來就非常方便,不用自己挨個去找依賴Jar包了。壞處是會引起Jar包衝突,
但當一個間接依賴存在多條引入路徑時,為了避免出現依賴重複的問題,Maven 通過依賴調節來確定間接依賴的引入路徑。
依賴調節遵循以下兩條原則:
引入路徑短者優先
先宣告者優先
引入路徑短者優先,顧名思義,當一個間接依賴存在多條引入路徑時,引入路徑短的會被解析使用。
例如,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 依賴調節的第一原則已經無法解決,需要使用第二原則:先宣告者優先。
最後,梳理一下Tomcat啟動時,對Jar包和類的載入順序,其中包含上面提到的不同種類的類載入器預設載入的目錄:
上述目錄中,同一資料夾下的Jar包,按照順序從上到下一次載入。如果一個class檔案已經被載入到JVM中,後面相同的class檔案就不會被載入了