掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
前言

網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)服務(wù)團(tuán)隊(duì)是一支充滿著熱情的團(tuán)隊(duì),執(zhí)著、敏銳、追求更好,是創(chuàng)新互聯(lián)的標(biāo)準(zhǔn)與要求,同時竭誠為客戶提供服務(wù)是我們的理念。創(chuàng)新互聯(lián)建站把每個網(wǎng)站當(dāng)做一個產(chǎn)品來開發(fā),精雕細(xì)琢,追求一名工匠心中的細(xì)致,我們更用心!
依賴沖突是日常開發(fā)中經(jīng)常碰到的過程,如果運(yùn)氣好,并不會有什么問題。偏偏阿粉有點(diǎn)背,碰到好幾次生產(chǎn)問題,排查一整晚,最后發(fā)現(xiàn)卻是依賴沖突的引起的問題。
沒碰到過這個問題同學(xué)可能沒什么感覺,阿粉舉兩個最近碰到例子,讓大家感受一些。
例子 1:
我們公司有個古老的業(yè)務(wù)基礎(chǔ)包 A。B,C 業(yè)務(wù)依賴這個包。某個團(tuán)隊(duì)拷貝 A 的部分代碼進(jìn)行重構(gòu),類名與路徑完全一樣,然后重新打包成 D 發(fā)布。
一次業(yè)務(wù)改動,B 業(yè)務(wù)也引入了 D 包,測試環(huán)境運(yùn)行的時候,一切 OK,但是在生產(chǎn)運(yùn)行時,卻拋出 NoSuchMethodError。
問題原因在于 B 業(yè)務(wù)依賴 A,D。而 A,D 存在兩個同包同名類,運(yùn)行的時候,具體加載誰,不同環(huán)境還真不一樣。
例子 2:
A 業(yè)務(wù)使用 Dubbo 進(jìn)行 RPC調(diào)用, Dubbo 需要依賴 javassist。當(dāng)前依賴關(guān)系為:
- A------->Dubbo------->javassist-3.18.1.GA
某次改動中引入另外一個第三方開源包,其依賴 javassist-3.15.0-GA 。生產(chǎn)發(fā)布的時候,將 javassist-3.15.0-GA 打包到應(yīng)用中,由于生產(chǎn)環(huán)節(jié)為 JDK1.8,從而導(dǎo)致運(yùn)行直接失敗。
除了上述問題,依賴沖突還可能導(dǎo)致應(yīng)用拋出 ClassNotFoundException,NoClassDefFoundError 等錯誤。
拋出錯誤這種情況還算好,還比較容易定位問題。怕就怕,不同版本同一個類內(nèi)部邏輯不同,從而導(dǎo)致業(yè)務(wù)異常。這種問題,真的很讓人抓狂,讓人頭禿。
[[315774]]
仔細(xì)分析依賴沖突,主要可以分為兩類:
下面我們分析一下依賴沖突產(chǎn)生的原因。
1 依賴沖突產(chǎn)生原因
1.1 依賴機(jī)制
Maven 依賴分為兩種情況,直接依賴與間接依賴,這個比較好理解,大家直接看圖就好。
1.2 仲裁機(jī)制
如果 A 應(yīng)用間接依賴多個 C 應(yīng)用,且版本都不一樣,Maven 將會通過仲裁機(jī)制選擇:
第一條原則,我們下面再說。
第二條原則,如下圖:
A 間接依賴兩個版本 E,這種情況下,由于 A 到 E-1.0 路徑最短,所以 A 中將會使用 E-1.0。
如果路徑恰好一樣,那么這種情況下 Maven 只能根據(jù) pom 中的順序,選擇最先聲明的,這也是個無奈的選擇。
1.3 scope 屬性
Maven 項(xiàng)目可以分為三個階段:編譯階段,測試階段,運(yùn)行階段了。通過 scope 屬性,我們可以決定依賴應(yīng)用是否參與以上階段,也將會影響依賴傳遞。
Maven 提供 6 種 scope :
compile
compile 是 Maven 默認(rèn)屬性,將會使依賴包參與項(xiàng)目的編譯,測試,運(yùn)行階段。當(dāng)然,項(xiàng)目打包之后將會包含該依賴。
provided
provided 意味著依賴僅參與項(xiàng)目編譯,測試的階段。若有如下依賴關(guān)系:
A----->B----->C
C 的 scope 為provided,C 將會參與 B 的編譯,測試階段,但是 C 不會傳遞給 A。如果 A 運(yùn)行過程需要 C,需要自己直接引入 C 依賴。典型如 Servlet API,因?yàn)?Tomcat 等容器內(nèi)部會提供。
runtime
runtime 代表依賴不再參與項(xiàng)目編譯階段,只參與測試,運(yùn)行階段。
若依賴不參與編譯階段,這種情況 IDE 中是無法導(dǎo)入相應(yīng)的類的。若存在依賴類,編譯過程中將會報(bào)錯。
典型的例子是 JDBC 驅(qū)動包,如 mysql :
mysql mysql-connector-java 6.0.6 runtime
知識點(diǎn):這個好處在于,只能使用 JDBC 標(biāo)準(zhǔn)接口,這樣就不會與特定的數(shù)據(jù)庫綁定。后續(xù)若切換數(shù)據(jù)庫,只需要更換 pom,然后修改相應(yīng)的參數(shù)即可。
test
test 僅參與測試階段的工作,典型的例子為 junit:
junit junit 4.12 test
system
system 與 provided 范圍一致,只不過 system 需要使用 systemPath 屬性指定本地路徑,而 provided 將會從 Maven 倉庫拉取。
import
import 比較特殊,不會參與以上階段運(yùn)行。其只能在 dependencyManagement下使用,且 type需要為 pom。典型的例子為 Spring-boot 依賴。
org.springframework.boot spring-boot-dependencies 2.1.6.RELEASE pom import
知識點(diǎn):通過這種方式,解決單繼承問題,也可以更好將依賴分類。
另外 Maven scope 將會影響依賴傳遞。
如果依賴關(guān)系為: A--->B--->C,A 依賴 B,B 依賴 C。最左列代表 B 的 scope 屬性,第一行代表 C 的 scope 屬性
如上所示,當(dāng) C 的 scope 為 provided/test, C 只在 B 中起作用,不會通過間接依賴傳遞給 A。
當(dāng)且僅當(dāng) B 的 scope 為 compile,且 C scope 為 runtime ,A 將會間接依賴 C,且 scope 為 runtime。其他情況下,C 的 scope 將會與 B 的 scope 一致。
2. 解決沖突的方法
2.1 使用 Maven 屬性控制依賴傳遞
依賴沖突時,根據(jù)錯誤日志,定位到?jīng)_突類,定位相應(yīng) jar 包,最后通過 excludes 排除相應(yīng)的包。
另外可以結(jié)合 IDEA Maven Helper 插件,主動檢查沖突依賴,提前排除。
通過插件,我們可以清晰看到?jīng)_突包,以及依賴路徑,還有相應(yīng)的 Scope。
除了排除依賴,我們可以通過合理的設(shè)置 scope 屬性,不讓依賴傳播下去。比如說,A 需要是使用 Spring-beans 包中某些類。如果其他項(xiàng)目鐵定會使用 Spring,那么我們可以將 A 中 Spring-beansscope 設(shè)置為 provided,讓其他項(xiàng)目自己選擇引入 Spring-beans 的版本。
這個適合公共基礎(chǔ)包,其他包不要隨便使用provided,若使用一定要寫清楚,使用過程中需要引入的依賴。
以上方法雖然治標(biāo),但是不治本。如果想依賴沖突不發(fā)生,我們需要提前建立一定的規(guī)范,團(tuán)隊(duì)一起遵守,才能有效避免該類問題。
3. 總結(jié)
如果我們把 NPE 問題當(dāng)做新手村普通怪物,那么依賴沖突問題就是人馬這種精英怪。剛開始遇到,我們會被虐的比較慘。只有我們不斷升級,學(xué)習(xí)掌握技巧,然后才能可以從容不迫解決。
ps:塞爾達(dá)中,你們第一次遇見人馬,打了幾次?阿粉記得那天整整從晚上九點(diǎn)打到凌晨兩點(diǎn),就是打不過啊~
4. 幫助文檔
Maven Dependency Scopes
Maven optional關(guān)鍵字透徹圖解

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流