詳解Maven JAR包沖突問題排查及解決方案
前言
寫這篇文章的初衷是因?yàn)榻裉煸谑褂胢vn dependency:tree命令時(shí),突然想起一年前面試阿里的一道面試題。面試題是說假設(shè)線上發(fā)生JAR包沖突,應(yīng)該怎么排查?我那時(shí)候的回答是IDEA有個(gè)Maven Helper的插件,可以幫忙分析依賴沖突,然后還有一種辦法是如果一個(gè)類import的時(shí)候提示兩個(gè)地方可導(dǎo)入,那就說明有沖突?,F(xiàn)在回頭想想確實(shí)太不專業(yè)了,以下是一次JAR包沖突的一個(gè)比較正規(guī)的流程,是通過整理幾篇博客后總結(jié)的希望對(duì)大家也有幫助,如果有錯(cuò)誤的地方也歡迎指出
GitHub地址:https://github.com/RobertoHuang
JAR沖突產(chǎn)生的原因
Pom.xml / \ B C / \ / \ X Y X M
在以上依賴關(guān)系中項(xiàng)目除了會(huì)引入B、C還會(huì)引入X、Y、M的依賴包,但是如果B依賴的X版本會(huì)1.0而C依賴的X版本為2.0時(shí),那最后項(xiàng)目使用的到底是X的1.0版本還是2.0版本就無法確定了。這是就要看ClassLoader的加載順序,假設(shè)ClassLoader先加載1.0版本那就不會(huì)加載2.0版本,反之同理
使用mvn -Dverbose dependency:tree排查沖突
[INFO] +- org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile [INFO] +- org.apache.tomcat:tomcat-jsp-api:jar:7.0.70:compile [INFO] | +- org.apache.tomcat:tomcat-el-api:jar:7.0.70:compile [INFO] | \- (org.apache.tomcat:tomcat-servlet-api:jar:7.0.70:compile - omitted for duplicate) [INFO] +- net.sf.jasperreports:jasperreports:jar:5.6.0:compile [INFO] | +- (commons-beanutils:commons-beanutils:jar:1.8.0:compile - omitted for conflict with 1.8.3) [INFO] | +- commons-collections:commons-collections:jar:3.2.1:compile [INFO] | +- commons-digester:commons-digester:jar:2.1:compile [INFO] | | +- (commons-beanutils:commons-beanutils:jar:1.8.3:compile - omitted for duplicate) [INFO] | | \- (commons-logging:commons-logging:jar:1.1.1:compile - omitted for duplicate)
遞歸依賴的關(guān)系列的算是比較清楚了,每行都是一個(gè)jar包,根據(jù)縮進(jìn)可以看到依賴的關(guān)系
- 最后寫著compile的就是編譯成功的
- 最后寫著omitted for duplicate的就是有JAR包被重復(fù)依賴了,但是JAR包的版本是一樣的
- 最后寫著omitted for conflict with xx的,說明和別的JAR包版本沖突了,該行的JAR包不會(huì)被引入
該命令可配合-Dincludes和-Dexcludes進(jìn)行使用,只輸出自己感興趣/不感興趣的JAR
參數(shù)格式為:[groupId]:[artifactId]:[type]:[version]
每個(gè)部分(冒號(hào)分割的部分)是支持*通配符的,如果要指定多個(gè)格式則可以用,分割,如:
mvn dependency:tree -Dincludes=javax.servlet,org.apache.*
解決沖突,使用exclusion標(biāo)簽將沖突的JAR排除
<dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.8.3.2</version> <exclusions> <exclusion> <artifactId>guava</artifactId> <groupId>com.google.guava</groupId> </exclusion> <exclusion> <artifactId>spring</artifactId> <groupId>org.springframework</groupId> </exclusion> </exclusions> </dependency>
解決了沖突后的樹(解決沖突的策略是:就近原則,即離根近的依賴被采納)
查看運(yùn)行期類來源的JAR包
有時(shí)你以為解決了但是偏偏還是報(bào)類包沖突,典型癥狀是java.lang.ClassNotFoundException或Method不兼容等異常,這時(shí)你可以設(shè)置一個(gè)斷點(diǎn),在斷點(diǎn)處通過下面這個(gè)工具類來查看Class所來源的JAR包
public class ClassLocationUtils { public static String where(final Class clazz) { if (clazz == null) { throw new IllegalArgumentException("null input: cls"); } URL result = null; final String clazzAsResource = clazz.getName().replace('.', '/').concat(".class"); final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (protectionDomain != null) { final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource != null) result = codeSource.getLocation(); if (result != null) { if ("file".equals(result.getProtocol())) { try { if (result.toExternalForm().endsWith(".jar") || result.toExternalForm().endsWith(".zip")) { result = new URL("jar:".concat(result.toExternalForm()).concat("!/").concat(clazzAsResource)); } else if (new File(result.getFile()).isDirectory()) { result = new URL(result, clazzAsResource); } } catch (MalformedURLException ignore) { } } } } if (result == null) { final ClassLoader clsLoader = clazz.getClassLoader(); result = clsLoader != null ? clsLoader.getResource(clazzAsResource) : ClassLoader.getSystemResource(clazzAsResource); } return result.toString(); } }
然后隨便寫一個(gè)測(cè)試設(shè)置好斷點(diǎn),在執(zhí)行到斷點(diǎn)出使用ALT+F8動(dòng)態(tài)執(zhí)行代碼,如
ClassLocationUtils.where(Logger.class)
即可馬上找到對(duì)應(yīng)的JAR,如果這個(gè)JAR不是你期望的就說明是IDE緩存造成的,清除緩存后重試即可
到此這篇關(guān)于詳解Maven JAR包沖突問題排查及解決方案的文章就介紹到這了,更多相關(guān)Maven JAR包沖突 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用Code128字體將文本轉(zhuǎn)換為code128條形碼
這篇文章主要介紹了如何使用Code128字體將文本轉(zhuǎn)換為code128條形碼 ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04Spring Cloud Zipkin服務(wù)端追蹤服務(wù)
這篇文章主要介紹了Spring Cloud Zipkin服務(wù)端追蹤服務(wù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java去掉小數(shù)點(diǎn)后面無效0的方案與建議
當(dāng)前小數(shù)點(diǎn)后面的位數(shù)過多的時(shí)候,多余的0沒有實(shí)際意義,下面這篇文章主要給大家介紹了關(guān)于Java去掉小數(shù)點(diǎn)后面無效0的方案與建議,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Spring Data MongoDB 數(shù)據(jù)庫批量操作的方法
在項(xiàng)目開發(fā)中經(jīng)常會(huì)批量插入數(shù)據(jù)和更新數(shù)據(jù)的操作,這篇文章主要介紹了Spring Data MongoDB 數(shù)據(jù)庫批量操作的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-11-11java版數(shù)獨(dú)游戲界面實(shí)現(xiàn)(二)
這篇文章主要為大家詳細(xì)介紹了java版數(shù)獨(dú)游戲界面實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12