
Java技術棧
www.javastack.cn
關注閱讀更多優質文章
前言
大家在項目中肯定有碰到過
Maven
的Jar包沖突問題,經常出現的場景為:
本地運作報
NoSuchMethodError
,
ClassNotFoundException
。明明在依賴裡有這個Jar包啊。怎麼運作不了!?
項目中明明定義着某個jar包版本為
2.0.2
,怎麼打包之後變成
2.5.0
了!?
A項目引xxx.jar包運作好好的,B項目同樣引入xxx.jar後,運作報錯了。。是B項目有問題,還是xxx.jar包有問題!?
本地環境和測試環境運作的好好的,到了生産就報一堆
NoSuchMethodError
,是我人品有問題還是生産環境有問題!?
這樣的問題如果不熟悉
maven
依賴機制的同學排查起來,估計挺頭痛的。
而且
maven
依賴結構不好的項目,在引入新的Jar包時的風險也是巨大的。小則影響性能,大則引起生産釋出和運作時異常。
其實以上問題的根源都來自于
Maven
的Jar包沖突和使用不當的依賴傳遞。這篇文章我就好好分析下以下3個内容:
- 依賴傳遞的原則和産生Jar包沖突的原理分析
- 定位沖突以及解決Jar包沖突的幾個簡單技巧
- 如何寫一個幹淨依賴關系的
檔案POM
依賴傳遞原則
幾乎所有的Jar包沖突都和依賴傳遞原則有關,是以我們先說
Maven
中的依賴傳遞原則:
最短路徑優先原則
假如引入了2個Jar包A和B,都傳遞依賴了Z這個Jar包:
A -> X -> Y -> Z(2.5)
B -> X -> Z(2.0)
那其實最終生效的是Z(2.0)這個版本。因為他的路徑更加短。如果我本地引用了Z(3.0)的包,那生效的就是3.0的版本。一樣的道理。
最先聲明優先原則
如果路徑長短一樣,優先選最先聲明的那個。
A -> Z(3.0)
B -> Z(2.5)
這裡A最先聲明,是以傳遞過來的Z選擇用3.0版本的。
Jar包沖突的原理
假設我們項目中依賴了A和B兩個Jar包。而A和B各自又有以下傳遞依賴
A -> X -> Z(2.0)
B -> X -> Y -> Z(2.5)
那最終系統中Z包就産生了沖突,2.0和2.5兩個版本沖突。但是classpath中隻會依賴一個版本的Z包。根據傳遞依賴的最短路徑優先原則,最終依賴的應該是2.0版本。
如果Y包中用了Z包2.5版本中新的method時候,當運作到這段邏輯的時候。就會報
NoSuchMethodError
了。因為本來依賴的是2.5版本,但是因為Jar包沖突
Maven
選擇了2.0版本,2.0版本中又沒有這個新的method,導緻出錯。
但要注意的是,不是所有沖突都會引起運作異常。相反,大部分公司的項目都會有一些Jar包沖突,但其實沒有造成運作時的問題。
這是因為很多傳遞依賴的Jar包,不管是2.0版本也好,2.5版本也好,都可以運作。
隻有高版本Jar包不向下相容,或者新增了某些低版本沒有的API才有可能導緻這樣的問題
定位沖突
IDEA提供了一個
maven
依賴分析神器:Maven Helper
用這個插件能很好的顯示出項目中所有的依賴樹和沖突
這裡面紅色高亮的部分,就表明這個Jar包有了沖突。選中這個jar包,可以看到這2個版本的沖突的來源。不會使用 IDEA 的可以關注公衆号Java技術棧在背景回複idea,可以擷取我整理的系列 IDEA 幹貨教程。
上圖的例子,表明
cruator-client
這個Jar包,有2個傳遞依賴,分别為2.5.0版本和4.0.1版本。沖突的描述為:
omitted for conflict with 2.5.0. 由于與2.5.0版本沖突而被省略
具體的層級在右邊也一目了然了,是以
maven
最終根據最短路徑優先原則選擇了2.5.0版本,4.0.1版本被忽略。
這時候有同學會問:本地環境我可以利用
Maven Helper
來定位,那麼預生産或者生産環境呢。又沒有IDEA,如何定位沖突的細節?
可以利用mvn指令來解決:
mvn dependency:tree -Dverbose
此處一定不要省略
參數,要不然是不會顯示被忽略的包的
-Dverbose
這篇《這 30 個常用的 Maven 指令》推薦看下,不會使用 Maven 的可以關注公衆号Java技術棧在背景回複maven,可以擷取我整理的系列 Maven幹貨教程。
其實mvn指令行一樣好用。非常清晰明确。
解決Jar包沖突的幾個實用技巧
排除法
還是上面的那個例子,現在生效的是2.5.0,如果想生效4.0.1。隻需要在2.5.0上面點
exclude
就行了。
版本鎖定法
如果很多個依賴都傳遞了Jar包A,涉及了很多個版本,但是你隻想指定一個版本。用排除法一個個去
exclude
太麻煩,而且
exclude
在pom檔案中也會展現,太多的話,也影響代碼整潔和閱讀感受。
這時候需要用到版本鎖定法
何謂版本鎖定法?公司的項目一般都會有父級pom,你想指定哪個版本隻需要在你項目的父POM中(當然在本工程内也可以)定義如下:(還是舉上個例子,指定4.0.1版本)
鎖定版本法可以打破2個依賴傳遞的原則,優先級為最高
鎖定版本後,依賴樹為:
都統一變成4.0.1,鎖定版本有一個好處:版本鎖定并不排除Jar包,而且顯示的把所有版本不一緻的Jar包變成統一一個版本,這樣在閱讀代碼時比較友好。也不用忍受一大堆的
exclude
标簽。
如何寫一個幹淨依賴關系的 POM
檔案
POM
我本人是有些輕度代碼潔癖的人,是以即便是pom檔案的依賴關系也想幹淨而整潔。如何寫好幹淨的POM呢,作者認為有幾點技巧要注意:
- 盡量在父POM中定義 ,來進行本項目一些依賴版本的管理,這樣可以從很大程度上解決一定的沖突
- 如果是提供給别人依賴的Jar包,盡可能不要傳遞依賴不必要的Jar包
- 使用
指令用于檢測那些聲明了但是沒被使用的依賴,如有有一些是你自己聲明的,那盡量去掉mvn dependency:analyze-only
- 使用
指令用來分析重複定義的依賴,清理那些重複定義的依賴mvn dependency:analyze-duplicate
最後
其實龐大的項目依賴傳遞也一定多。但是不管多複雜的依賴關系,看到不要害怕。就這麼幾條原則,細心的去分析,所有的依賴都有迹可循。
這些傳遞依賴如果管理的好,能讓你的維護成本大大降低。如果管不好,這群野孩子每一個都可能是引發下一個
NoSuchMethodError
的導火索。
最近熱文: 1、重磅!《Java開發手冊(嵩山版)》最新釋出 2、打破你的認知!Java空指針居然還能這樣玩 3、盤點 35 個 Apache 頂級項目,我拜服了… 4、Spring Boot 太狠了,一次釋出 3 個版本! 5、Spring Boot 如何快速內建 Redis? 6、盤點 6 個被淘汰的 Java 技術,曾經風光過! 7、Spring Boot Redis 實作分布式鎖,真香! 8、Spring Boot 幹掉了 Maven 擁抱 Gradle! 9、公司來了個新同僚不會用 Lombok! 10、同僚寫了個隐藏 bug,我排查了 3 天! 掃碼關注 Java技術棧 公衆号閱讀更多幹貨。
點選「」擷取面試題大全~