一招解決所有依賴衝突

2022-11-29 15:01:49

背景介紹

最近遇到了這樣一個問題,我們有一個 jar 包 common-tool,作為基礎工具包,被各個專案在參照。突然某一天發現紀錄檔很多報錯。

一看是 NoSuchMethodError,意思是 DisJunction 裡 init 方法沒找到,但是我檢查了程式碼是有這個方法的啊。

問題定位

當時百度了一下,都說這種情況一般是 jar 包衝突了,但是本地看來下沒有 jar 包版本都一致,沒有衝突啊,百思不得其解。於是寫了個程式碼看看這個報找不到方法的類到底有沒有這個方法。

Constructor<?>[] constructors = Disjunction.class.getConstructors();
    for (Constructor constructor:constructors){
    //檢視類的構造器方法
       log.error("find monitor bug:{}",constructor);
    }
    Class targetclass = Disjunction.class;//可以用自己想知道的類替換
    String className = targetclass.getName();
    className = className.replace('.', '/');
    String resource = "/" + className + ".class";
    URL url = targetclass.getResource(resource);
    //檢視類的全路徑
    log.error("find monitor bug:{}",url.getFile());

列印出來後發現類全路徑竟然不是我的包裡的,是一個 agent.jar 裡面的。

問了下才知道原來是運維新增了一個 agent 到線上環境,裡面的 byte-buddy 依賴和我的衝突了。

如果是本地衝突的話,也可以使用 IDEA 裡的 Maven Helper 外掛,它可以清晰的指出某個包被哪幾個包依賴。

解決方案:

在看解決方案之前先來回顧一下Maven的依賴原則:

  • 最短路徑優先

即如果我 A->B->C->X1.0,D->E->X2.0,我專案裡引入了 A 和 D,那麼我的 X 版本是用的 2.0,因為 X2.0 路徑最短。

  • 申明順序優先

如果 A-B-X(1.0) ,A-C-X(2.0) 這樣的路徑長度一樣怎麼辦呢?這樣的情況下,maven 會根據 pom 檔案宣告的順序載入,如果先宣告了 B,後宣告了 C,那就最後的依賴就會是 X(1.0)

解決方案 1:

maven 依賴的順序是路徑優先,所以我們可以在專案的 pom 檔案裡直接申明版本,這樣就比專案裡的引入的 jar 包裡再引入的依賴優先順序要高些。

這種方案的缺點就是因為我這個基礎 jar 包好幾個專案再用,需要每個專案都要修改。

解決方案 2:

使用 maven 外掛 maven-shade-plugin

 <plugin>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <includes>
                                   //替換的範圍,只替換下面兩個包 <include>net.bytebuddy:byte-buddy</include>
                                    <include>net.bytebuddy:byte-buddy-agent</include>
                                </includes>
                            </artifactSet>
                            <createSourcesJar>true</createSourcesJar>
                            <relocations>
                                <relocation>
                            // 將net.bytebuddy依賴重新命名為cn.mmc.net.bytebuddy
                            <pattern>net.bytebuddy</pattern>
                                    <shadedPattern>cn.mmc.net.bytebuddy</shadedPattern>
                                </relocation>
                            </relocations>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

這種做法就是將原有依賴的 jar 包都重新命名一下,比如裡面的類是 net.bytebuddy.DisJunction,用這個外掛後就變為了 cn.mmc.net.bytebuddy.DisJunction,這樣類的全路徑不一樣,就徹底杜絕了依賴衝突的情況。

另外上面的 includes 是指僅指定替換某些依賴裡包名是 net.bytebuddy,否則所有包含了 net.bytebuddy 的都會被重新命名,這樣打出來的 jar 包就會非常大。

總結

為了一勞永逸,我使用了第二種方案 maven-shade-plugin 的方式,釋出之後依賴衝突的問題就解決了。