Maven解決Jar包依賴沖突方案分析

一翔曲、前言

早前,筆者在碰到ClassNotFoundException的異常也是一臉懵逼蛀恩,但是這個類確實存在于我們的項目中疫铜,覺很離奇茂浮,就厚著臉皮去問組里的大神勛哥,勛哥開始一臉鄙視,心想這個居然都不知道席揽,抵不過我的再三追問顽馋,勛哥拋出一句tree看一下,雖然我還是不懂幌羞,但是不好意思再問下去寸谜,感覺再問下去會被打死。事后勛哥過來問我属桦,那個依賴沖突的問題解決了沒有熊痴,解決了的話,給大家分享一下解決方案聂宾,后來因為各種原因果善,一直沒有完成那個分享,所以借這篇文章補上吧系谐。

二巾陕、正文

2.1 表象

Jar包沖突作為一個老生常談的問題,幾乎每一個程序員都會遇到纪他。jar包沖突通常發(fā)生在程序編譯時或運行時鄙煤。主要分為兩類:一類比較直觀也是最常見的,在運行時拋出各種異常茶袒,還有一類比較隱晦梯刚,它不會直接報錯,但是程序的行為卻和預(yù)期不一致薪寓,羅列如下:

  • java.lang.ClassNotFoundException乾巧,即找不到指定的java類。
  • java.lang.NoSuchMethodError预愤,即找不到指定的方法沟于。
  • java.lang.NoClassDefFoundError,即找不到指定的java類(運行時報錯)植康。
  • 沒有異常旷太,但是程序的行為和預(yù)期不一致。

如果有上述行為销睁,就很有可能出現(xiàn)了包沖突的問題供璧。

2.2 原理

在正式談?wù)撊绾谓鉀Q這一問題之前,我們不妨先來研究下為什么會出現(xiàn)包沖突的問題冻记。很顯然睡毒,當我們使用Maven作為包依賴的管理工具的時候,如果我們直接或者間接的引入了groupId和artifactId都相同的包時冗栗,maven究竟是怎么選擇最終使用哪個version的包來進行打包的呢演顾?

2.2.1 傳遞依賴沖突

依賴傳遞:
情形1:如果A依賴B供搀,并且A頁依賴C,那么引入A钠至,意味著B和C都會被引入葛虐。

image.png

情形2:如果A依賴B,B依賴C棉钧,那么引入A屿脐,意味著B和C都會被引入。


image.png

Maven引入的傳遞性依賴機制宪卿,一方面大大簡化和方便了依賴聲明的诵,另一方面,大部分情況下我們只需要關(guān)心項目的直接依賴是什么佑钾,而不用考慮這些直接依賴會引入什么傳遞性依賴奢驯。但有時候,這種傳遞性依賴會造成問題次绘。

例如瘪阁,項目中有這樣的依賴關(guān)系:A->B->D(1.0)、A->C->D(1.2)邮偎,D是B和C的傳遞性依賴管跺,但是兩條依賴路徑上有兩個版本的D,最終哪個D會被Maven解析使用呢禾进?

image.png

Maven最終選擇哪個這里暫不給結(jié)論豁跑,假設(shè)最終選擇的是D-1.0,但是我們在代碼編寫的時候使用到了與D1.0中就有的某個類泻云,但是該類在D1.2中新增的某個方法的時候艇拍,在編寫時代碼不會報錯,但是一旦編譯運行就會報錯java.lang.NoSuchMethodError宠纯。

2.2.2 依賴調(diào)解原則

2.2.2.1 路徑最近者優(yōu)先原則

Maven依賴調(diào)解(Dependency Mediation)的第一原則是:路徑最近者優(yōu)先卸夕。

如果項目的依賴圖如下圖所示:D(1.0)的路徑長度為2,而D(1.2)的路徑長度為3婆瓜,因此D(1.0)會被解析使用快集。

image.png

2.2.2.2 第一聲明者優(yōu)先原則

依賴調(diào)解第一原則不能解決所有問題。例如下面這個例子廉白,D的兩個版本到達A的兩條依賴路徑長度都為2个初。那么到底誰會被解析使用呢?在Maven 2.0.8及之前的版本中猴蹂,結(jié)果是不確定的院溺;但是從Maven 2.0.9開始,為了盡可能避免構(gòu)建的不確定性磅轻,Maven定義了依賴調(diào)解的第二原則:第一聲明者優(yōu)先珍逸,即需要找到在pom文件聲明中逐虚,依賴B的聲明是寫在了C的前面還是后面,如果依賴B的聲明寫在前面弄息,那么D-1.0有效,否則就是D1-2有效勤婚。這種原則會解決不確定性的問題摹量,但是有時候我們需要使用到類的功能也會因為這一原則而使用不了。

image.png

2.2.3 小結(jié)

在大多數(shù)時候馒胆,依賴沖突可能并不會對系統(tǒng)造成什么異常缨称,因為Maven始終選擇了一個Jar包來使用。但是祝迂,不排除在某些特定條件下睦尽,會出現(xiàn)類似找不到類的異常,所以型雳,只要存在依賴沖突当凡,在我看來,最好還是解決掉纠俭,不要給系統(tǒng)留下隱患沿量。

2.3 解決方案

2.3.1 尋找沖突依賴

2.3.1.1 mvn dependency : tree指令

第一步:找到傳遞依賴的鬼出在哪里?

dependency:tree是把照妖照冤荆,pom.xml用它照照朴则,所有傳遞性依賴都將無處遁形,并且會以層級樹方式展現(xiàn)钓简,非常直觀乌妒。以下就是執(zhí)行dependency:tree后的一個輸出:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ euler-foundation ---
[INFO] com.hsit:euler-foundation:jar:0.9.0.1-SNAPSHOT
[INFO] +- com.rop:rop:jar:1.0.1:compile
[INFO] |  +- org.slf4j:slf4j-api:jar:1.7.5:compile
[INFO] |  +- org.slf4j:slf4j-log4j12:jar:1.7.5:compile
[INFO] |  +- log4j:log4j:jar:1.2.16:compile
[INFO] |  +- commons-lang:commons-lang:jar:2.6:compile
[INFO] |  +- commons-codec:commons-codec:jar:1.6:compile
[INFO] |  +- javax.validation:validation-api:jar:1.0.0.GA:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:4.2.0.Final:compile
[INFO] |  +- org.codehaus.jackson:jackson-core-asl:jar:1.9.5:compile
[INFO] |  +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.5:compile
[INFO] |  +- org.codehaus.jackson:jackson-jaxrs:jar:1.9.5:compile
[INFO] |  +- org.codehaus.jackson:jackson-xc:jar:1.9.5:compile
[INFO] |  \- com.fasterxml.jackson.dataformat:jackson-dataformat-xml:jar:2.2.3:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-core:jar:2.2.3:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-annotations:jar:2.2.3:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-databind:jar:2.2.3:compile
[INFO] |     +- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.2.3:compile
[INFO] |     \- org.codehaus.woodstox:stax2-api:jar:3.1.1:compile
[INFO] |        \- javax.xml.stream:stax-api:jar:1.0-2:compile

剛才吹噓dependency:tree時,我用到了“無處遁形”外邓,其實有時你會發(fā)現(xiàn)簡單地用dependency:tree往往并不能查看到所有的傳遞依賴撤蚊。不過如果你真的想要看所有的,必須得加一個-Dverbose參數(shù)损话,這時就必定是最全的了拴魄。

內(nèi)容太多,眼花繚亂席镀,有沒有好法呢匹中?當然有了,加上Dincludes或者Dexcludes制定小包含或者排除的包豪诲,dependency:tree就會幫你過濾出來:
引用

Dincludes=org.springframework:spring-tx

過濾串使用groupId:artifactId:version的方式進行過濾顶捷,可以不用寫全,例如:

mvn dependency:tree -Dverbose -Dincludes=asm:asm  

就會出來asm依賴包的分析信息:

[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ ridge-test ---
[INFO] com.ridge:ridge-test:jar:1.0.2-SNAPSHOT
[INFO] +- asm:asm:jar:3.2:compile
[INFO] \- org.unitils:unitils-dbmaintainer:jar:3.3:compile
[INFO]    \- org.hibernate:hibernate:jar:3.2.5.ga:compile
[INFO]       +- cglib:cglib:jar:2.1_3:compile
[INFO]       |  \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2)
[INFO]       \- (asm:asm:jar:1.5.3:compile - omitted for conflict with 3.2)
[INFO] ------------------------------------------------------------------------

對asm有依賴有一個直接的依賴(asm:asm:jar:3.2)還有一個傳遞進入的依賴(asm:asm:jar:1.5.3)

2.3.1.2 Maven依賴結(jié)構(gòu)圖

可以使用IDEA提供的方法——Maven依賴結(jié)構(gòu)圖屎篱,打開Maven窗口服赎,選擇Dependencies葵蒂,然后點擊那個圖標(Show Dependencies)或者使用快捷鍵(Ctrl+Alt+Shift+U),即可打開Maven依賴關(guān)系結(jié)構(gòu)圖

image.png

在圖中重虑,我們可以看到有一些紅色的實線践付,這些紅色實線就是依賴沖突,藍色實線則是正常的依賴缺厉。

image.png

2.3.1.3 IDEA Maven Helper插件

首先永高,按照常規(guī)的IDEA 插件安裝的方式安裝插件Maven Helper:


image.png

安裝的過程可能會出現(xiàn)下面的報錯,是因為插件和idea的版本不兼容提针,換個插件版本就好了命爬。

image.png

在插件安裝好之后,我們打開pom.xml文件辐脖,在底部會多出一個Dependency Analyzer選項饲宛。


image.png

點開這個選項,找到?jīng)_突嗜价,點擊右鍵艇抠,然后選擇Exclude即可排除沖突版本的Jar包。

  • Conflicts:顯示所有的沖突的依賴
  • All dependencys as List:以列表的形式顯示所有的依賴
  • All dependencys as tree:以樹的形式顯示所有的依賴
image.png

注意:
同一個jar包可能需要執(zhí)行多次Exclude操作久锥,因為可能有多處沖突练链。
執(zhí)行Exclude之后需要點擊"Refresh"刷新一下,才能確定是否依然存在沖突奴拦。

2.3.2 處理沖突依賴

2.3.2.1 加載提前

在清楚了Maven的依賴調(diào)解規(guī)則后媒鼓,我可以很自然地想到解決方案,就是把我們需要的版本的路徑縮短或者聲明提前错妖。如下圖绿鸣,比如我們明確需要使用D-1.2,那么我們可以明確在pom依賴中暂氯,手動引入D-1.2包潮模,并且將D-1.2的依賴聲明寫在依賴A的前面即可:

image.png

2.3.2.2 排除依賴

也就是使用exclusions元素聲明排除其中一個依賴,exclusions可以包含一個或者多個exclusion子元素痴施,因此可以排除一個或者多個傳遞性依賴擎厢。需要注意的是,聲明exclusion的時候只需要groupId和artifactId辣吃,而不需要version元素动遭,這是因為只需要groupId和artifactId就能唯一定位依賴圖中的某個依賴。換句話說神得,Maven解析后的依賴中厘惦,不可能出現(xiàn)groupId和artifactId相同,但是version不同的兩個依賴哩簿。

<dependency>
                <groupId>com.alibaba.aecp</groupId>
                <artifactId>logger-formatter</artifactId>
                <version>${logger-formatter.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>com.taobao.eagleeye</groupId>
                        <artifactId>eagleeye-core</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

2.3.2.3 升級父節(jié)點

使用上面三種方法都有一個前提宵蕉,那就是你選定的version是可以兼容兩個沖突的jar酝静。但是兩個jar不兼容的話,針對這種情況羡玛, 去掉任何一個依賴别智,都會出現(xiàn)異常。

針對這種情況稼稿, 去掉任何一個依賴薄榛,都會出現(xiàn)異常 。接口升級引入了新二方包渺杉,導(dǎo)致項目中間接依賴了slf4j-api:1.5.11和slf4j-api:1.7.5蛇数,結(jié)果這兩個包還不兼容挪钓,1.7.5新增了一些類是越,同時把1.5.11中一些方法簽名改了,結(jié)果這些變動的類和方法都被引用了碌上。最后倚评,使用maven helper查看1.5.11的整個依賴樹,找到其父節(jié)點馏予,升級其父節(jié)點version天梧,這樣父節(jié)點依賴的slf4j-api的version也會跟著變,找到一個能兼容的版本即可。

image.png

2.3.2.4 全路徑?jīng)_突

還有一種特殊的沖突,多個dependency的groupID或artifactID不同(或兩者都不同)秽荤,但包中存在全路徑類名相同的類Java類加載器根據(jù)classpath加載類時灿渴,根據(jù)classpath中jar包出現(xiàn)的先后順序進行查找類并緩存,后面jar包中的類不使用被去。這個時候的常見異常就是NoSuchMethodException,NoClassDefFoundError,ClassNotFoundException挫酿,NoSuchMethodError等。

如果其中一個jar是我們不需要的愕难,那么排除它就行了早龟。但是,如果這個jar被很多dependency依賴猫缭,你需要一個個去寫exclusions是不是很麻煩葱弟。這時我們可以直接在pom中添加一個空依賴(和想要去掉的jar的groupID,artifactID相同猜丹,但是version不同的一個空項目打包上傳到遠程倉庫中)翘悉。

<!-- ================================================= -->
<!-- 排除依賴 -->
<!-- ================================================= -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>999-not-exist-v3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>999-not-exist</version>
</dependency>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市居触,隨后出現(xiàn)的幾起案子妖混,更是在濱河造成了極大的恐慌老赤,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件制市,死亡現(xiàn)場離奇詭異抬旺,居然都是意外死亡,警方通過查閱死者的電腦和手機祥楣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門开财,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人误褪,你說我怎么就攤上這事责鳍。” “怎么了兽间?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵历葛,是天一觀的道長。 經(jīng)常有香客問我嘀略,道長恤溶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任帜羊,我火速辦了婚禮咒程,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讼育。我一直安慰自己帐姻,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布奶段。 她就那樣靜靜地躺著饥瓷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忧饭。 梳的紋絲不亂的頭發(fā)上扛伍,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音词裤,去河邊找鬼刺洒。 笑死,一個胖子當著我的面吹牛吼砂,可吹牛的內(nèi)容都是我干的逆航。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渔肩,長吁一口氣:“原來是場噩夢啊……” “哼因俐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抹剩,失蹤者是張志新(化名)和其女友劉穎撑帖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澳眷,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡胡嘿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了钳踊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衷敌。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拓瞪,靈堂內(nèi)的尸體忽然破棺而出缴罗,到底是詐尸還是另有隱情,我是刑警寧澤祭埂,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布面氓,位于F島的核電站,受9級特大地震影響沟堡,放射性物質(zhì)發(fā)生泄漏侧但。R本人自食惡果不足惜矢空,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一航罗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屁药,春花似錦粥血、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缭嫡,卻和暖如春缔御,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妇蛀。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工耕突, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人评架。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓眷茁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纵诞。 傳聞我的和親對象是個殘疾皇子上祈,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355