實際上手體驗maven面對衝突Jar包的載入規則

2023-07-19 12:00:41

一、問題背景

相信大家在日常的開發過程中都遇到過Jar包衝突的問題,emm,在最近處理業務需求時我也遇到了不同版本jar包衝突導致專案載入出錯的問題。主要是一個完整的專案會不可避免的使用第三方的Jar包來實現功能開發,各種第三方包之間可能會存在依賴關係,不同版本的依賴就會可能導致依賴間的相互衝突,進而導致整個專案載入的失敗。

這篇文章主要記錄了本次遇到的問題:即maven在面對不同版本的jar包在pom檔案中同時宣告會存在載入覆蓋的問題,於是通過查詢網上相關資料對maven包的載入規則介紹,並通過實際場景對其進行分析驗證;

二、maven載入原則

1.最短路徑原則:面對多級(兩級及以上)的不同依賴,會優先選擇路徑最短的依賴;

2.宣告優先原則:面對多級(兩級及以上)的同級依賴,先宣告的依賴會覆蓋後宣告的依賴;

3.同級依賴中,後宣告的依賴會覆蓋先宣告的依賴;

三、本地驗證maven載入原則

1.最短路徑原則:使用最短路徑載入的前提是,專案中存在兩級以上的不同依賴jar包,此時專案會優先載入路徑最短的jar包;

範例驗證: 分別在common模組和service模組中間接和直接的引入不同版本的elasticsearch-rest-client,觀察專案中面對不同路徑長度情況下實際載入時所使用的版本情況。

common模組:common模組中引入elasticsearch-rest-high-level-client 依賴包, 而該依賴包它引入了 elasticsearch-rest-client 7.4.2, 從而實現在common模組中間接參照該包;

common的pom檔案:

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>

service模組: 為了驗證不同路徑長度下maven的包載入順序 我們在service模組中直接引入elasticsearch-rest-client 6.8.13;

service的pom檔案:

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>

實際載入結果:在IDEA中載入pom檔案時,可以在maven管理中看到已經提示jar包衝突;

mvn dependency:tree: 我們可以通過mvn dependency :tree命令來檢視該專案的依賴樹,觀察發現實際載入的版本是elasticsearch-rest-client 6.8.13,符合maven中的最短路徑優先原則;

  1. 宣告優先原則:宣告優先原則的前提是對於兩級以上的同級依賴,先宣告的依賴會覆蓋後宣告的依賴包;

範例驗證: 針對該原則的驗證場景構造不再關注模組是否直接或者間接參照不同版本的es,我們在common模組和service模組中都直接參照不同版本的es,然後通過改變兩個模組在pom檔案中宣告的先後順序來觀察專案啟動後實際載入的jar包;

common模組:在common模組中直接引入依賴包elasticsearch-rest-client 7.4.2

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>

service模組:在service模組中引入依賴包elasticsearch-rest-client 6.8.13

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>

實際載入結果:

▪場景1:我們將common模組在pom檔案中先引入,然後將在service模組置於common模組後面引入,觀察專案實際載入情況;

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

▪觀察載入結果圖,發現實際載入的是es-rest-client 7.4.2, 即確實是common模組宣告生效,service模組後宣告導致其中的es未被載入。符合宣告優先原則;

◦場景2:我們將service模組在pom檔案中先引入,然後將在common模組置於service模組後面引入,觀察專案實際載入情況;;

    <dependencies>
         <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>backend_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

▪觀察專案實際載入結果圖,發現實際載入的是es-rest-client 6.8.13, 即確實是模組宣告生效,common模組後宣告導致其中的es未被載入。發現符合宣告優先原則;

◦宣告優先原則場景驗證結束

3. 同級依賴中後載入覆蓋先載入原則

範例驗證: 為了構造在同級依賴中的載入場景 我們在專案中直接引入兩個不同es版本的依賴,然後同樣通過改變兩個es版本在pom中的宣告順序來觀察專案實際載入的es版本。

▪場景1:我們首先驗證client 7.4.2依賴包在client 6.8.13之前宣告的情況;

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>
    </dependencies>

▪觀察maven的實際載入結果如下,發現專案中實際載入的es-rest-client 版本是6.8.13,先宣告的7.4.2版本並未實際載入到專案中。符合同級依賴中後載入覆蓋先載入原則。

▪場景2:然後我們改變宣告順序,將client 6.8.13依賴包在client 7.4.2之前宣告;

    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.8.13</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.4.2</version>
        </dependency>
    </dependencies>

▪觀察maven實際載入結果如下,發現專案中實際載入的es-rest-client 版本是7.4.2,先宣告的6.8.13版本並未實際載入到專案中。符合同級依賴中後載入覆蓋先載入原則。

四、常見異常

****Jar發生衝突後在程式啟動時常見異常報錯, 下面四種異常是能夠直觀表徵Jar包載入衝突

◦程式丟擲java.lang.ClassNotFoundException異常;

◦程式丟擲java.lang.NoSuchMethodError異常;

◦程式丟擲java.lang.NoClassDefFoundError異常;

◦程式丟擲java.lang.LinkageError異常等;

五、總結

之前只是淺層的瞭解maven包的載入,沒有結合具體的載入原則進行系統的學習驗證,正好通過需求開發中遇到依賴衝突相關問題對maven的載入原則進行探究。ok,明白啦!

作者:京東科技 宋慧超

來源:京東雲開發者社群