大家在項(xiàng)目中肯定有碰到過Maven
的Jar包沖突問題奸汇,經(jīng)常出現(xiàn)的場景為:
本地運(yùn)行報(bào)NoSuchMethodError
,ClassNotFoundException
。明明在依賴?yán)镉羞@個(gè)Jar包啊送膳。怎么運(yùn)行不了F账椤吼肥?
項(xiàng)目中明明定義著某個(gè)jar包版本為2.0.2
,怎么打包之后變成2.5.0
了B槌怠缀皱?
A項(xiàng)目引xxx.jar包運(yùn)行好好的,B項(xiàng)目同樣引入xxx.jar后动猬,運(yùn)行報(bào)錯(cuò)了啤斗。。是B項(xiàng)目有問題枣察,還是xxx.jar包有問題U肌燃逻?
本地環(huán)境和測試環(huán)境運(yùn)行的好好的,到了生產(chǎn)就報(bào)一堆NoSuchMethodError
臂痕,是我人品有問題還是生產(chǎn)環(huán)境有問題2蟆?
這樣的問題如果不熟悉maven
依賴機(jī)制的同學(xué)排查起來握童,估計(jì)挺頭痛的姆怪。
而且maven
依賴結(jié)構(gòu)不好的項(xiàng)目,在引入新的Jar包時(shí)的風(fēng)險(xiǎn)也是巨大的澡绩。小則影響性能稽揭,大則引起生產(chǎn)發(fā)布和運(yùn)行時(shí)異常。
其實(shí)以上問題的根源都來自于Maven
的Jar包沖突和使用不當(dāng)?shù)囊蕾噦鬟f肥卡。這篇文章我就好好分析下以下3個(gè)內(nèi)容:
- 依賴傳遞的原則和產(chǎn)生Jar包沖突的原理分析
- 定位沖突以及解決Jar包沖突的幾個(gè)簡單技巧
- 如何寫一個(gè)干凈依賴關(guān)系的
POM
文件
依賴傳遞原則
幾乎所有的Jar包沖突都和依賴傳遞原則有關(guān)溪掀,所以我們先說Maven
中的依賴傳遞原則:
最短路徑優(yōu)先原則
假如引入了2個(gè)Jar包A和B,都傳遞依賴了Z這個(gè)Jar包:
A -> X -> Y -> Z(2.5)
B -> X -> Z(2.0)
那其實(shí)最終生效的是Z(2.0)這個(gè)版本步鉴。因?yàn)樗穆窂礁佣叹疚浮H绻冶镜匾昧薢(3.0)的包,那生效的就是3.0的版本氛琢。一樣的道理喊递。
最先聲明優(yōu)先原則
如果路徑長短一樣,優(yōu)先選最先聲明的那個(gè)阳似。
A -> Z(3.0)
B -> Z(2.5)
這里A最先聲明骚勘,所以傳遞過來的Z選擇用3.0版本的。
Jar包沖突的原理
假設(shè)我們項(xiàng)目中依賴了A和B兩個(gè)Jar包撮奏。而A和B各自又有以下傳遞依賴
A -> X -> Z(2.0)
B -> X -> Y -> Z(2.5)
那最終系統(tǒng)中Z包就產(chǎn)生了沖突俏讹,2.0和2.5兩個(gè)版本沖突。但是classpath中只會(huì)依賴一個(gè)版本的Z包挽荡。根據(jù)傳遞依賴的最短路徑優(yōu)先原則藐石,最終依賴的應(yīng)該是2.0版本。
如果Y包中用了Z包2.5版本中新的method時(shí)候定拟,當(dāng)運(yùn)行到這段邏輯的時(shí)候于微。就會(huì)報(bào)NoSuchMethodError
了。因?yàn)楸緛硪蕾嚨氖?.5版本青自,但是因?yàn)镴ar包沖突Maven
選擇了2.0版本株依,2.0版本中又沒有這個(gè)新的method,導(dǎo)致出錯(cuò)延窜。
但要注意的是恋腕,不是所有沖突都會(huì)引起運(yùn)行異常。相反逆瑞,大部分公司的項(xiàng)目都會(huì)有一些Jar包沖突荠藤,但其實(shí)沒有造成運(yùn)行時(shí)的問題伙单。
這是因?yàn)楹芏鄠鬟f依賴的Jar包,不管是2.0版本也好哈肖,2.5版本也好吻育,都可以運(yùn)行。
只有高版本Jar包不向下兼容淤井,或者新增了某些低版本沒有的API才有可能導(dǎo)致這樣的問題
定位沖突
IDEA提供了一個(gè)maven
依賴分析神器:Maven Helper
用這個(gè)插件能很好的顯示出項(xiàng)目中所有的依賴樹和沖突
這里面紅色高亮的部分布疼,就表明這個(gè)Jar包有了沖突。選中這個(gè)jar包币狠,可以看到這2個(gè)版本的沖突的來源游两。不會(huì)使用 IDEA 的可以關(guān)注公眾號(hào)Java技術(shù)棧在后臺(tái)回復(fù)idea,可以獲取我整理的系列 IDEA 干貨教程漩绵。
上圖的例子贱案,表明cruator-client
這個(gè)Jar包,有2個(gè)傳遞依賴渐行,分別為2.5.0版本和4.0.1版本轰坊。沖突的描述為:
omitted for conflict with 2.5.0. 由于與2.5.0版本沖突而被省略
具體的層級(jí)在右邊也一目了然了,所以maven
最終根據(jù)最短路徑優(yōu)先原則選擇了2.5.0版本祟印,4.0.1版本被忽略。
這時(shí)候有同學(xué)會(huì)問:本地環(huán)境我可以利用Maven Helper
來定位粟害,那么預(yù)生產(chǎn)或者生產(chǎn)環(huán)境呢蕴忆。又沒有IDEA,如何定位沖突的細(xì)節(jié)悲幅?
可以利用mvn命令來解決:
mvn dependency:tree -Dverbose
此處一定不要省略
-Dverbose
參數(shù)套鹅,要不然是不會(huì)顯示被忽略的包的
這篇《這 30 個(gè)常用的 Maven 命令》推薦看下,不會(huì)使用 Maven 的可以關(guān)注公眾號(hào)Java技術(shù)棧在后臺(tái)回復(fù)maven汰具,可以獲取我整理的系列 Maven干貨教程卓鹿。
其實(shí)mvn命令行一樣好用。非常清晰明確留荔。
解決Jar包沖突的幾個(gè)實(shí)用技巧
排除法
還是上面的那個(gè)例子吟孙,現(xiàn)在生效的是2.5.0,如果想生效4.0.1聚蝶。只需要在2.5.0上面點(diǎn)exclude
就行了杰妓。
版本鎖定法
如果很多個(gè)依賴都傳遞了Jar包A,涉及了很多個(gè)版本碘勉,但是你只想指定一個(gè)版本巷挥。用排除法一個(gè)個(gè)去exclude
太麻煩,而且exclude
在pom文件中也會(huì)體現(xiàn)验靡,太多的話倍宾,也影響代碼整潔和閱讀感受雏节。
這時(shí)候需要用到版本鎖定法
何謂版本鎖定法?公司的項(xiàng)目一般都會(huì)有父級(jí)pom高职,你想指定哪個(gè)版本只需要在你項(xiàng)目的父POM中(當(dāng)然在本工程內(nèi)也可以)定義如下:(還是舉上個(gè)例子矾屯,指定4.0.1版本)
<dependencyManagement> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>4.0.1</version> </dependency></dependencyManagement>
鎖定版本法可以打破2個(gè)依賴傳遞的原則,優(yōu)先級(jí)為最高
鎖定版本后初厚,依賴樹為:
都統(tǒng)一變成4.0.1件蚕,鎖定版本有一個(gè)好處:版本鎖定并不排除Jar包,而且顯示的把所有版本不一致的Jar包變成統(tǒng)一一個(gè)版本产禾,這樣在閱讀代碼時(shí)比較友好排作。也不用忍受一大堆的exclude
標(biāo)簽。
如何寫一個(gè)干凈依賴關(guān)系的POM
文件
我本人是有些輕度代碼潔癖的人亚情,所以即便是pom文件的依賴關(guān)系也想干凈而整潔妄痪。如何寫好干凈的POM呢,作者認(rèn)為有幾點(diǎn)技巧要注意:
- 盡量在父POM中定義
<dependencymanagement style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"></dependencymanagement>
楞件,來進(jìn)行本項(xiàng)目一些依賴版本的管理衫生,這樣可以從很大程度上解決一定的沖突 - 如果是提供給別人依賴的Jar包,盡可能不要傳遞依賴不必要的Jar包
- 使用
mvn dependency:analyze-only
命令用于檢測那些聲明了但是沒被使用的依賴土浸,如有有一些是你自己聲明的罪针,那盡量去掉 - 使用
mvn dependency:analyze-duplicate
命令用來分析重復(fù)定義的依賴,清理那些重復(fù)定義的依賴
最后
其實(shí)龐大的項(xiàng)目依賴傳遞也一定多黄伊。但是不管多復(fù)雜的依賴關(guān)系泪酱,看到不要害怕。就這么幾條原則还最,細(xì)心的去分析墓阀,所有的依賴都有跡可循。
這些傳遞依賴如果管理的好拓轻,能讓你的維護(hù)成本大大降低斯撮。如果管不好,這群野孩子每一個(gè)都可能是引發(fā)下一個(gè)NoSuchMethodError
的導(dǎo)火索扶叉。