用好這幾個技巧,解決Maven Jar包衝突易如反掌
阿新 • • 發佈:2020-07-31
## 前言
大家在專案中肯定有碰到過`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**
![file](https://img2020.cnblogs.com/other/268224/202007/268224-20200731105745597-1407821956.jpg)
用這個外掛能很好的顯示出專案中所有的依賴樹和衝突
![file](https://img2020.cnblogs.com/other/268224/202007/268224-20200731105745927-1606262915.jpg)
這裡面紅色高亮的部分,就表明這個Jar包有了衝突。選中這個jar包,可以看到這2個版本的衝突的來源。
上圖的例子,表明`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`引數,要不然是不會顯示被忽略的包的
![file](https://img2020.cnblogs.com/other/268224/202007/268224-20200731105746364-1812968681.jpg)
其實mvn命令列一樣好用。非常清晰明確。
## 解決Jar包衝突的幾個實用技巧
**排除法**
還是上面的那個例子,現在生效的是2.5.0,如果想生效4.0.1。只需要在2.5.0上面點`exclude`就行了。
![file](https://img2020.cnblogs.com/other/268224/202007/268224-20200731105746654-980773024.jpg)
**版本鎖定法**
如果很多個依賴都傳遞了Jar包A,涉及了很多個版本,但是你只想指定一個版本。用排除法一個個去`exclude`太麻煩,而且`exclude`在pom檔案中也會體現,太多的話,也影響程式碼整潔和閱讀感受。
這時候需要用到版本鎖定法
何謂版本鎖定法?公司的專案一般都會有父級pom,你想指定哪個版本只需要在你專案的父POM中(當然在本工程內也可以)定義如下:(還是舉上個例子,指定4.0.1版本)
```xml
```
**鎖定版本法可以打破2個依賴傳遞的原則,優先順序為最高**
鎖定版本後,依賴樹為:
![file](https://img2020.cnblogs.com/other/268224/202007/268224-20200731105746897-189717888.jpg)
都統一變成4.0.1,鎖定版本有一個好處:版本鎖定並不排除Jar包,而且顯示的把所有版本不一致的Jar包變成統一一個版本,這樣在閱讀程式碼時比較友好。也不用忍受一大堆的`exclude`標籤。
## 如何寫一個乾淨依賴關係的`POM`檔案
我本人是有些輕度程式碼潔癖的人,所以即便是pom檔案的依賴關係也想幹淨而整潔。如何寫好乾淨的POM呢,作者認為有幾點技巧要注意:
* 儘量在父POM