廣州網(wǎng)站建設(shè)哪里買四平網(wǎng)絡(luò)推廣
準(zhǔn)備工作
1、創(chuàng)建一個(gè)空工程 maven_dependency_conflict_demo,在 maven_dependency_conflict_demo 創(chuàng)建不同的 Maven 工程模塊,用于演示本文的一些點(diǎn)。
什么是依賴沖突?
- 當(dāng)引入同一個(gè)依賴的多個(gè)不同版本時(shí),就會發(fā)生依賴沖突。
演示 1
步驟:
1、在 maven_dependency_conflict_demo 中創(chuàng)建 2 個(gè)基于 Maven 的 JavaSE 項(xiàng)目 maven_00 和 maven_01。
2、在 maven_01 的 pom.xml 文件中引入 junit 4.12 版本。
3、將 maven_01 打包安裝到私服。
4、在 maven_00 的 pom.xml 文件中引入 junit 4.13 版本和 maven_01。
結(jié)果如下圖所示,可以看到,的的確確發(fā)生了依賴沖突,而且 Maven 自己處理了依賴沖突。Maven 處理沖突依據(jù)的是短路徑優(yōu)先原則,后面會提到。
Maven的依賴調(diào)解原則
Maven 在遇到依賴沖突時(shí),會先根據(jù)路徑最短優(yōu)先和聲明順序優(yōu)先兩大原則處理沖突。
1、短路徑優(yōu)先
當(dāng)一個(gè)間接依賴存在多條引入路徑時(shí),引入路徑短的會被解析使用。
見演示 1,依賴關(guān)系圖如下圖所示:
附:代碼分支 2411291-larryla-maven-name-first。
2、聲明順序優(yōu)先
如果存在短路徑,則優(yōu)先選擇短路徑。如果路徑相同,則先聲明者優(yōu)先。
演示 2
步驟:
1、在 maven_dependency_conflict_demo 中創(chuàng)建 3 個(gè)基于 Maven 的 JavaSE 項(xiàng)目 maven_02、maven_03 和 maven_04。
2、在 maven_03 中引入 junit 4.12 版本。
3、將 maven_03 打包安裝到私服。
4、在 maven_04 中引入 junit 4.13 版本。
5、將 maven_04 打包安裝到私服。
6、在 maven_02 中引入 maven_03 和 maven_04,查看 junit 版本引入情況。
上面步驟對應(yīng)的依賴關(guān)系圖如下:
結(jié)果如下:
(1) 若 maven_03 在 maven_04 前面,則 maven_02 中引入的是 junit 4.12
(2) 若 maven_04 在 maven_03 前面,則 maven_02 中引入的是 junit 4.13
附:代碼分支 2411291-larryla-maven-name-first。
3、特殊情況
(1) 子 pom 覆蓋父 pom。
如果在父工程和子工程引入了相同依賴的不同版本,那么子工程中的版本會覆蓋覆蓋父工程中的版本。
步驟:
1、在 maven_dependency_conflict_demo 中創(chuàng)建 2 個(gè)基于 Maven 的 JavaSE 項(xiàng)目 maven_07 和 maven_08。
2、在 maven_07 中引入 junit 4.13 版本。
3、在 maven_08 重新指定的 junit 為 4.12 版本。
結(jié)果如下圖所示,可以看到,子工程引入的是 junit 4.12 版本,而不是繼承自父工程的 4.13 版本,這說明子工程覆蓋了父工程的版本。
(2) 同一個(gè) pom.xml 文件里配置相同資源的不同版本,后配置的覆蓋先配置的。
在 maven_07 的 pom.xml 文件中先后引入 MyBatis 3.5.11 和 3.5.7 版本,結(jié)果如下圖??梢钥吹?#xff0c;后聲明的 3.5.7 版本覆蓋了先聲明的 3.5.11 版本。
排除依賴
情景
如果僅僅依靠 Maven 來進(jìn)行依賴調(diào)解,在很多情況下是不適用的,這時(shí)需要我們手動排除依賴。
舉個(gè)例子,當(dāng)前項(xiàng)目存在下面的依賴關(guān)系:
依賴鏈路一:A -> B -> C -> X(2.0.0) // dist = 3
依賴鏈路二:A -> D -> X(1.0.0) // dist = 2
根據(jù)路徑最短優(yōu)先原則,X(1.0) 會被解析使用,也就是說 A 中實(shí)際用的是 1.0 版本的 X。但是,這樣可能會導(dǎo)致錯(cuò)誤。
假如 X(2.0) 相較于 X(1.0) 新增了 1 個(gè)類和 1 個(gè)方法,并且 A 中使用了新增的類和方法,編譯不通過。
問題演示
步驟:
1、在 maven_dependency_conflict_demo 中創(chuàng)建 5 個(gè)基于 Maven 的 JavaSE 項(xiàng)目 maven_a、maven_b、maven_c 、maven_d 和 maven_x。
2、在 maven_x 的 1.0 版本打包安裝到私服的 Release 倉庫。
3、將 maven_x 的 2.0 版本打包安裝到私服的 Release 倉庫。
4、在 maven_c 中引入 maven_x 的 2.0 版本;將 maven_c 打包安裝到私服的 Release 倉庫。在 maven_b 中引入 maven_c;將 maven_b 打包安裝到私服的 Release 倉庫。在 maven_a 中引入 maven_b。
5、在 maven_d 中引入 maven_x 的 1.0 版本,將 maven_d 打包安裝到私服的 Release 倉庫。在 maven_a 中引入 maven_d。
上面步驟對應(yīng)的依賴關(guān)系圖如下:
maven_x 1.0.0 版本:
在 2.0.0 版本中,變化如下:
- 在 BallSports 類中,新增方法 playFootBall。
- 新增類 SwimmingSports,類中包含方法 FreeStyle。
結(jié)果如下圖,依據(jù)短路徑優(yōu)先原則,在 maven_a 模塊中引入了 maven_x 1.0.0 版本,舍棄了 maven_x 2.0.0 版本,所以無法使用 2.0.0 版本中新增的類和方法。
解決方案
一般在解決依賴沖突的時(shí)候,我們都會優(yōu)先保留版本較高的,這是因?yàn)榇蟛糠?jar 在升級的時(shí)候都會做到向下兼容。
1、exclusions標(biāo)簽
具體做法是,在 maven_a 的 pom.xml 文件中使用 exclusion 標(biāo)簽顯式排除掉 maven_x 1.0.0 版本的依賴。結(jié)果如下圖所示,成功引入 maven_x 2.0.0 版本,舍棄 1.0.0 版本。
2、optional標(biāo)簽
我們還可以使用 optional 標(biāo)簽,對外隱藏當(dāng)前所依賴的資源。
具體做法是,在 maven_d 中引入 maven_x 1.0.0 版本時(shí)指定 optional 標(biāo)簽值為 true。
結(jié)果如下圖所示,可以看到,使用 optional 標(biāo)簽可以使 maven_x 1.0.0 版本的 jar 包不再通過 maven_d 傳遞,也就是 maven_a 在引入 maven_b 時(shí),得不到 maven_x 1.0.0 的 jar 包。
附:代碼分支 2411292-larryla-maven-dependency-exclusion。
對比:標(biāo)簽和標(biāo)簽的區(qū)別
由上可知,在 maven_a → maven_d → maven_x 這條分支上,我們有兩種解決依賴沖突的方法,即 exclusions 標(biāo)簽和 optional 標(biāo)簽。兩種方法的本質(zhì)都是不將 maven_x 1.0.0 jar 包傳遞到 maven_a 中。但區(qū)別在于:
- 排除依賴是在 maven_a 引入 maven_d 依賴時(shí),同時(shí)設(shè)置
<exclusions>
,maven_a 知道 maven_x 1.0.0 的存在,主動將其排除掉。這種適用于不能修改 maven_d 的配置文件的情況。 - 可選依賴是在 maven_d 上為其依賴項(xiàng) maven_x 設(shè)置
<optional>
,是 maven_d 不讓"外界"知道依賴 maven_x 的存在,不再傳遞依賴 maven_x,此時(shí),maven_a 不知道 maven_x 1.0.0 的存在。這種適用于可以修改 maven_d 的配置文件的情況。
思考 1
在"問題演示"中,我們復(fù)現(xiàn)了短路徑優(yōu)先可能導(dǎo)致的問題,即 maven_a 會引入 maven_x 1.0.0 版本,舍棄 maven_x 2.0.0 版本。那么問題來了,既然引入了 maven_x 1.0.0 版本,那么 maven_b 和 maven_c 會用哪個(gè)版本的 maven_x 呢?
結(jié)果如下,由編輯區(qū)的方法調(diào)用和右側(cè)的依賴管理可以得知,maven_a 為了解決依賴沖突選擇引入 maven_x 1.0.0 版本,而 maven_b 和 maven_c 不受 maven_a 的影響,引入的依舊是 maven_x 2.0.0 版本:
因此,上面給出的依賴傳遞圖不是很精確的,應(yīng)該如下:
- maven_a 引入 maven_x 1.0.0
- maven_b、maven_c 引入 maven_x 2.0.0
附:代碼分支 2411292-larryla-maven-dependency-exclusion。
思考 2
對于"情景"一節(jié),Maven 在依據(jù)短路徑優(yōu)先原則調(diào)解沖突時(shí)可能會帶來一些問題。假如 X(2.0) 相較于 X(1.0) 新增了 1 個(gè)類和 1 個(gè)方法:
- A 中使用了新增的類,編譯不通過。
- A 中使用了新增的方法,編譯不通過。
我們在"問題演示"一節(jié)復(fù)現(xiàn)了上面兩個(gè)問題,并且在"解決方案"一節(jié)中使用兩種方案 exclusions 和 optional,引入了高版本 maven_x 2.0.0 解決了這個(gè)問題。
但是,引入高版本就沒有問題了嗎?
還是下圖的依賴關(guān)系,假如 maven_a 引入了 maven_x 2.0.0 版本。
假如 maven_x 2.0.0 相比于 maven_x 1.0.0 版本刪掉了一個(gè)類,但 maven_a 已經(jīng)引用了,這時(shí)會報(bào)錯(cuò);maven_x 2.0.0 相比于 maven_x 1.0.0 版本刪掉了一個(gè)方法,但 maven_a 已經(jīng)引用了,同樣也會報(bào)錯(cuò)。
演示
我們重新組織 maven_x 1.0.0 和 maven_x 2.0.0 版本包括的內(nèi)容。
maven_x 1.0.0 版本內(nèi)容如下:
-
BallSports:playBasketBall 方法、playFootBall 方法
-
SwimmingSports:FreeStyle 方法
maven_x 1.0.0 版本內(nèi)容變化如下: -
刪掉 BallSports 類中的 playBasketBall 方法
-
刪掉 SwimmingSports 類
步驟:
1、在 maven_x 的 1.0 版本打包安裝到私服的 Release 倉庫。
2、將 maven_x 的 2.0 版本打包安裝到私服的 Release 倉庫。
3、在 maven_c 中引入 maven_x 的 2.0 版本;將 maven_c 打包安裝到私服的 Release 倉庫。在 maven_b 中引入 maven_c;將 maven_b 打包安裝到私服的 Release 倉庫。在 maven_a 中引入 maven_b。
4、在 maven_d 中引入 maven_x 的 1.0 版本,將 maven_d 打包安裝到私服的 Release 倉庫。在 maven_a 中引入 maven_d。
結(jié)果如下,可以看到 maven_a 中引入的是 maven_x 2.0.0 版本,無法使用 2.0.0 版本相比 1.0.0 版本刪掉的方法和類,直接編譯不通過。
附:代碼分支 2411301-larryla-maven-dependency-exclusion-high-ver-problem。
聚合工程,統(tǒng)一管理版本
聚合工程:一個(gè)項(xiàng)目允許創(chuàng)建多個(gè)子模塊,多個(gè)子模塊組成一個(gè)整體,可以統(tǒng)一進(jìn)行項(xiàng)目的構(gòu)建。要弄明白聚合工程,得先清楚"父子工程"的概念:
- 父工程:沒有任何代碼、僅有
pom.xml
的空項(xiàng)目,用來定義公共依賴、插件和配置; - 子工程:編寫具體代碼的子項(xiàng)目,可以繼承父工程的依賴項(xiàng)、配置,還可以獨(dú)立拓展。
而 Maven 聚合工程,就是基于父子工程結(jié)構(gòu),將一個(gè)完整項(xiàng)目,劃分出不同的層次。這種方式可以很好的管理多模塊之間的依賴關(guān)系,以及構(gòu)建順序,大大提高了開發(fā)效率、維護(hù)性。
為了防止不同子工程引入不同版本的依賴,在父工程中,統(tǒng)一對依賴的版本進(jìn)行控制,規(guī)定所有子工程都使用同一版本的依賴,可以使用<dependencyManagement>
標(biāo)簽來管理。
子工程在使用<dependencyManagement>
中已有的依賴項(xiàng)時(shí),不需要寫<version>
版本號,版本號在父工程中統(tǒng)一管理,這樣做的好處在于:以后為項(xiàng)目的技術(shù)棧升級版本時(shí),不需要單獨(dú)修改每個(gè)子工程的POM
,只需要修改父POM
文件即可,減少了版本沖突的可能性。
演示
步驟:
1、在 maven_dependency_conflict_demo 中創(chuàng)建 2 個(gè)基于 Maven 的 JavaSE 項(xiàng)目 maven_05 和 maven_06。
2、在 maven_05 中引入 junit 4.13 版本,使用 dependencyManagement 標(biāo)簽統(tǒng)一管理。
3、在 maven_06 繼承 maven_05 的 junit 4.13 版本。
上面步驟對應(yīng)的依賴關(guān)系圖如下:
結(jié)果如下圖所示,可以看到,在 maven_06 工程中,只需要指定 groupId 和 artifactId,不需要指定 version,就可以從父工程 maven_05 中繼承 junit 4.13 依賴。
其他
1、子工程繼承父工程的依賴時(shí),可以指定版本,這時(shí)子工程的版本會覆蓋父工程的版本。
演示:
還是使用"演示"一節(jié)的模塊。在 maven_06 模塊中指定 junit 的版本為 4.12,那么 maven_06 引入的 junit 將是 4.12 版本,而不是父工程的 4.13 版本:
雖然可以在子工程中指定其他版本,但不建議這么做。如果這樣做的話,使用 dependencyManagement 的意義就不大了。
2、父工程可以使用 properties 標(biāo)簽指定依賴的版本,然后在 GAV 坐標(biāo)中引入該版本。這樣在升級依賴版本時(shí),只需要修改 properties 標(biāo)簽中變量的值,而無需修改 GAV 中的 version 標(biāo)簽的值。
演示:
還是使用"演示"一節(jié)的模塊。在 maven_05 的 pom.xml 文件中,在 properties 標(biāo)簽中自定義一個(gè) junit-version 標(biāo)簽,用于指定 junit 依賴的版本號。然后,在 version 標(biāo)簽中使用 ${junit-version} 的形式聲明版本號。如下圖所示,maven_06 依舊成功引入了 junit 4.13 版本。
3、dependencyManagement 標(biāo)簽只是用于聲明依賴,不會在項(xiàng)目中引入依賴。
如下圖所示,只是在聚合工程的子工程中引入了 junit 依賴,父工程并未引入依賴。
4、dependencyManagement 和 dependencies 的區(qū)別
<dependencies>
:定義強(qiáng)制性依賴,寫在該標(biāo)簽里的依賴項(xiàng),子工程必須繼承。<dependencyManagement>
:定義可選性依賴,該標(biāo)簽里的依賴項(xiàng),子工程可選擇性地繼承。
附:代碼分支 2411302-larryla-maven-dependency-uni-manage
排查依賴沖突
在 MySQL 中,我們會借助一些工具進(jìn)行慢 SQL 分析。那么,在 Maven 中,我們是否可以借助一些工具進(jìn)行依賴沖突定位呢?
我們可以在插件市場下載"Maven Helper" 插件輔助排查依賴沖突。
在"排除依賴-情景"一節(jié)中,我們分析了如下依賴沖突情況,得到的結(jié)論是 Maven 會依據(jù)短路徑優(yōu)先原則引入 X(1.0) 版本。
依賴鏈路一:A -> B -> C -> X(2.0.0) // dist = 3
依賴鏈路二:A -> D -> X(1.0.0) // dist = 2
接下來,我們使用 Maven Helper 插件來驗(yàn)證一下。
打開 maven_a 的 pom.xml 文件,在下方點(diǎn)擊 Dependency Analyzer,然后點(diǎn)擊 Refresh UI,會彈出如下界面:
提示有一個(gè)依賴沖突,即 maven_x: 1.0.0。我們點(diǎn)開看看,發(fā)現(xiàn)與之沖突的是 maven_x 2.0.0。這就和上面呼應(yīng)上了,Maven 依據(jù)短路徑優(yōu)先原則,選擇了 1.0.0 版本,舍棄了 2.0.0 版本。
在"排除依賴-解決方案"一節(jié)中,我們基于排除依賴引入了 2.0.0 版本,這里,我們同樣保留 2.0.0 版本。在 1.0.0 上右鍵,選擇 exclude,表示排除掉該版本。然后點(diǎn)擊上面的 Reimport,再點(diǎn)擊 Refresh UI。
返回到 pom.xml 文件,發(fā)現(xiàn)點(diǎn)擊上面的 exclude 按鈕,就是在 pom.xml 文件中使用了 exclusions 標(biāo)簽:
思維導(dǎo)圖
代碼倉庫:git@github.com:Acura-bit/maven_dependency_conflict_demo.git