天天看點

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

依賴沖突是日常開發中經常碰到的過程,如果運氣好,沒碰到過這個問題同學可能沒什麼感覺,并不會有什麼問題。

沒碰到過這個問題同學可能沒什麼感覺,小編舉兩個最近碰到例子,讓大家感受一些。

舉個栗子1:

有個古老的業務基礎包 A。B業務,C業務依賴這個包A。某個團隊拷貝 A包 的部分代碼進行重構,類名與路徑完全一樣,然後重新打包成 D 釋出。

一次業務改動,B 業務也引入了 D 包,測試環境運作的時候,一切 OK,但是在生産運作時,卻抛出 NoSuchMethodError。

問題原因在于 B 業務依賴 A,D。而 A,D 存在兩個同包同名類,運作的時候,具體加載誰,不同環境還真不一樣。

舉個栗子2:

A 業務使用 Dubbo 進行 RPC調用, Dubbo 需要依賴 javassist。目前依賴關系為:

A------->Dubbo------->javassist-3.18.1.GA
           

某次改動中引入另外一個第三方開源包,其依賴 javassist-3.15.0-GA 。生産釋出的時候,将 javassist-3.15.0-GA 打包到應用中,由于生産環節為 JDK1.8,進而導緻運作直接失敗。

除了上述問題,依賴沖突還可能導緻應用抛出 ClassNotFoundException,NoClassDefFoundError 等錯誤。

抛出錯誤這種情況還算好,還比較容易定位問題。怕就怕,不同版本同一個類内部邏輯不同,進而導緻業務異常。這種問題,真的很讓人抓狂,讓人頭秃。

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

仔細分析依賴沖突,主要可以分為兩類:

  • 項目同一依賴應用,存在多版本,每個版本同一個類,可能存在差異。
  • 項目不同依賴應用,存在包名,類名完全一樣的類。

下面我們分析一下依賴沖突産生的原因。

1. 依賴沖突原因

1.1 依賴機制

Maven 依賴分為兩種情況,直接依賴與間接依賴,這個比較好了解,大家直接看圖就好。

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

1.2 仲裁機制

如果 A 應用間接依賴多個 C 應用,且版本都不一樣,Maven 将會通過仲裁機制選擇:

  • 優先按照依賴管理元素中指定的版本聲明進行仲裁時,下面的兩個原則都無效了
  • 短路徑優先
  • 聲明優先(若路徑相同,将看 pom 中聲明的順序)。

第一條原則,我們下面再說。

第二條原則,如下圖:

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

A 間接依賴兩個版本 E,這種情況下,由于 A 到 E-1.0 路徑最短,是以 A 中将會使用 E-1.0。

如果路徑恰好一樣,那麼這種情況下 Maven 隻能根據 pom 中的順序,選擇最先聲明的,這也是個無奈的選擇。

1.3 scope 屬性

Maven 項目可以分為三個階段:編譯階段,測試階段,運作階段了。通過 scope 屬性,我們可以決定依賴應用是否參與以上階段,也将會影響依賴傳遞。

Maven 提供 6 種 scope :

  • compile
  • provided
  • runtime
  • test
  • system
  • import
  • compile

compile 是 Maven 預設屬性,将會使依賴包參與項目的編譯,測試,運作及打包階段。當然,項目打包之後将會包含該依賴。

  • provided

provided 意味着依賴僅參與項目編譯,測試的階段。若有如下依賴關系:

A----->B----->C
           

C 的 scope 為provided,C 将會參與 B 的編譯,測試階段,但是 C 不會傳遞給 A。如果 A 運作過程需要 C,需要自己直接引入 C 依賴。典型如 Servlet API,因為 Tomcat 等容器内部會提供。

  • runtime

runtime 代表依賴不再參與項目編譯階段,隻參與測試,運作階段。

若依賴不參與編譯階段,這種情況 IDE 中是無法導入相應的類的。若存在依賴類,編譯過程中将會報錯。

典型的例子是 JDBC 驅動包,如 mysql :

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結
知識點:這個好處在于,隻能使用 JDBC 标準接口,這樣就不會與特定的資料庫綁定。後續若切換資料庫,隻需要更換 pom,然後修改相應的參數即可。
  • test

test 僅參與測試階段的工作,典型的例子為 junit:

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結
  • system

system 與 provided 範圍一緻,隻不過 system 需要使用 systemPath 屬性指定本地路徑,而 provided 将會從 Maven 倉庫拉取。

  • import

import 比較特殊,不會參與以上階段運作。其隻能在 dependencyManagement下使用,且 type需要為 pom。典型的例子為 Spring-boot 依賴。

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結
知識點:通過這種方式,解決單繼承問題,也可以更好将依賴分類。

另外 Maven scope 将會影響依賴傳遞。

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結
如果依賴關系為: A--->B--->C,A 依賴 B,B 依賴 C。最左列代表 B 的 scope 屬性,第一行代表 C 的 scope 屬性

如上所示,當 C 的 scope 為 provided/test, C 隻在 B 中起作用,不會通過間接依賴傳遞給 A。

當且僅當 B 的 scope 為 compile,且 C scope 為 runtime ,A 将會間接依賴 C,且 scope 為 runtime。其他情況下,C 的 scope 将會與 B 的 scope 一緻。

2. 解決沖突的方法

2.1 使用 Maven 屬性控制依賴傳遞

依賴沖突時,根據錯誤日志,定位到沖突類,定位相應 jar 包,最後通過 excludes 排除相應的包。

另外可以結合 IDEA Maven Helper 插件,主動檢查沖突依賴,提前排除。

Maven項目jar包依賴沖突的原因以及解決辦法1. 依賴沖突原因2. 解決沖突的方法3. 總結

通過插件,我們可以清晰看到沖突包,以及依賴路徑,還有相應的 Scope。

除了排除依賴,我們可以通過合理的設定 scope 屬性,不讓依賴傳播下去。

比如說,A 需要是使用 Spring-beans 包中某些類。如果其他項目鐵定會使用 Spring,那麼我們可以将 A 中 Spring-beansscope 設定為 provided,讓其他項目自己選擇引入 Spring-beans 的版本。

這個适合公共基礎包,其他包不要随便使用provided,若使用一定要寫清楚,使用過程中需要引入的依賴。

以上方法雖然治标,但是不治本。如果想依賴沖突不發生,我們需要提前建立一定的規範,團隊一起遵守,才能有效避免該類問題。

  1. 應用項目中使用 dependencyManagement統一管理基礎依賴,定義統一的版本,如常用中間包,工具包,日志包。
  2. 二方包中不要引入無關的依賴,做到盡量少的依賴。團隊開發中,比較常見情況是二方包繼承公共的父 pom,進而導緻繼承許多無相關的依賴,這種情況可以單獨管理。
  3. 二方包做好向下相容,不要随意改動現有類名,方法名,字段名。
  4. 項目應用上線之前,将 SNAPSHOT 替換成正式版本。雖然 SNAPSHOT 修改起來很友善,但是正因為這個特性,可以被随便修改。如果某次生産打包釋出不注意,就會引入。
  5. 二方包不要使用同一個包名,類名。一般來說,團隊開發中,包名,類名一樣機率比較小。這種比較容易出現在一些重構項目,複制原來類,重構打包釋出。對于情況下可以修改包名。如 cmomon-lang3 是 common-lang 更新版, cmomon-lang3包名為 org.apache.commons.lang3,而 common-lang 包名為 org.apache.commons.lang

3. 總結

如果我們把 NPE 問題當做新手村普通怪物,那麼依賴沖突問題就是人馬這種精英怪。剛開始遇到,我們會被虐的比較慘。隻有我們不斷更新,學習掌握技巧,然後才能可以從容不迫解決。