為什麼 Maven 依賴項的順序很重要
一、簡介
Maven 是 Java 生態系統中最受歡迎的建置和依賴管理工具之一。雖然它提供了一種在專案中聲明和使用不同框架和程式庫的便捷方法,但在處理應用程式中的許多依賴項時可能會出現一些意想不到的問題。
為了避免此類編譯時和執行時間問題,我們將學習如何正確配置依賴項清單。在本教程中,我們還將提到可用於偵測和修復不同版本工件之間不一致的工具。
2. 依賴機制
首先,讓我們簡單回顧一下 Maven 的主要概念。
如前所述,專案物件模型 (POM) 配置 Maven 專案。此 XML 檔案包含有關專案的詳細資訊以及建置該專案所需的配置資訊。
pom.xml
檔案中宣告的外部函式庫稱為相依性。每個依賴項都是使用一組識別碼( groupId
、 artifactId
和version
)唯一定義的,通常稱為座標。 Maven 的內部機制會自動從中央儲存庫解析和下載依賴項,使它們在我們的專案中可用。
複雜的專案通常有多個依賴項,其中一些依賴其他庫。那些在專案的 pom 中明確列出的稱為直接依賴項。另一方面,直接依賴項稱為傳遞依賴項,Maven 會自動包含它們。
讓我們更詳細地解釋一下。
2.1.傳遞依賴
傳遞依賴只是依賴的依賴。讓我們來說明一下:
如上面的範例圖所示,我們的程式碼庫X
依賴其他幾個項目。 X
的依賴項之一是專案B
,它依賴專案L
和D
這兩個L
和D
是X
的傳遞依賴。如果D
也依賴N
,則N
成為X
的另一個傳遞依賴項。另一方面,專案G
是X
的直接依賴項,沒有額外的依賴項,因此路徑上不會包含傳遞性。
Maven 的關鍵功能之一是它能夠管理傳遞依賴項。如果我們的專案依賴一個函式庫,而該函式庫又依賴其他函式庫,那麼 Maven 將無需追蹤編譯和運行應用程式所需的所有依賴項。
在上面的範例中,我們將依賴項B
和G
加入到pom.xml
中,Maven 將處理其餘的事情。這對於大型企業應用程式特別有用,因為它們可能有數百個依賴項。在這種情況下,除了具有重複的依賴項之外,多個直接依賴項可能需要同一 JAR 檔案的不同版本。換句話說,我們可能存在著無法協同工作的依賴關係。
幸運的是,其他一些 Maven 功能可以派上用場來限制包含的依賴項。
2.2.依賴性調解
我們在其他教程中介紹了其中的大部分內容 - 依賴項管理、範圍、排除和可選依賴項。在這裡,我們將重點放在依賴中介。
讓我們看看實際效果:
在上面的範例中,Maven 將透過路徑X -> D 2.0
選擇最接近依賴D
**( X
)**的 D 版本 – 版本 D D 2.0
來解決衝突。
在路徑X -> G -> D 2.0
中, D
是傳遞依賴,但由於它與直接D
依賴是同一版本,因此由於重複而被省略。其他傳遞依賴項,即X -> B -> D 1.0
和X -> N -> L -> D 1.0
,由於衝突而被省略,因為它們的版本與直接依賴項D
不同。
另一方面,如果X
沒有將D
聲明為直接依賴項,則依賴項X -> B -> D 1.0
和X -> G -> D 2.0
將具有相同的樹深度。當兩個依賴項在依賴項樹中處於同一層級時,第一個聲明獲勝,因此最終建置中將使用D 1.0
。
3. 依賴順序問題
為了示範上述場景,讓我們切換到一個使用通用函式庫的更實際的範例 - Apache POI 和 OpenCSV。兩者都依賴 Apache Commons Collections,因此我們的專案添加此庫作為傳遞依賴項。
3.1.實際例子
我們將有意選擇舊版的 Apache POI 和 OpenCSV 依賴項,即使用不同版本的 Apache Commons Collections 的依賴項:
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.2</version>
</dependency>
</dependencies>
此外,兩個commons-collections4
庫位於依賴樹的相同深度。我們可以使用mvn dependency:tree
指令來驗證這一點。有了它,我們可以列出專案中的所有依賴項,包括直接依賴項和傳遞依賴項:
mvn dependency:tree -Dverbose
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ dependency-ordering ---
[INFO] com.baeldung:dependency-ordering:jar:0.0.1-SNAPSHOT
[INFO] +- org.apache.poi:poi:jar:5.3.0:compile
[INFO] | \- org.apache.commons:commons-collections4:jar:4.4:compile
...
[INFO] +- com.opencsv:opencsv:jar:4.2:compile
[INFO] | \- (org.apache.commons:commons-collections4:jar:4.1:compile - omitted for conflict with 4.4)
...
正如我們所看到的,該專案將使用4.4版本。在版本 4.1 和 4.4 之間,發生了多項變更。除此之外,在版本 4.2 中,也加入了MapUtils.size(Map<?, ?>)
方法。
讓我們在一個簡單的測試中使用它來演示依賴順序問題:
@Test
void whenCorrectDependencyVersionIsUsed_thenShouldCompile() {
assertEquals(0, MapUtils.size(new HashMap<>()));
}
程式碼編譯成功並且測試通過。現在讓我們更改pom.xml
檔案中依賴項的順序並重新編譯程式碼。
因此,會出現以下錯誤:
java: cannot find symbol
symbol: method size(java.util.HashMap<java.lang.Object,java.lang.Object>)
location: class org.apache.commons.collections4.MapUtils
此外,我們可以再次使用mvn dependency:tree
指令來檢查是否有任何變更:
$ mvn dependency:tree -Dverbose
[INFO] com.baeldung:dependency-ordering:jar:0.0.1-SNAPSHOT
[INFO] +- com.opencsv:opencsv:jar:4.2:compile
[INFO] | \- org.apache.commons:commons-collections4:jar:4.1:compile
...
[INFO] +- org.apache.poi:poi:jar:5.3.0:compile
[INFO] | \- (org.apache.commons:commons-collections4:jar:4.4:compile - omitted for conflict with 4.1)
...
我們的程式碼現在使用舊版的commons-collections4
函式庫,該函式庫沒有MapUtils.size
方法。
3.2.指示依賴關係解析問題的常見異常
除了cannot find symbol
錯誤之外,依賴性問題還可以透過多種方式表現出來。以下是一些最常遇到的情況:
-
NoSuchFieldError
, -
NoSuchMethodError
, -
NoSuchMethodException
, -
ClassNotFoundException,
-
NoClassDefFoundError
自訂和核心 Maven 插件還需要依賴項才能執行特定目標。如果出現問題,我們將收到以下錯誤:
[ERROR] Failed to execute goal (…) on project (…): Execution (…) of goal (…) failed: A required class was missing while executing (…)
不幸的是,並非所有與依賴相關的異常都發生在編譯時。
4. 解決依賴問題的工具
幸運的是,有多種工具可以幫助確保運行時安全。
4.1. Maven 依賴插件
Apache Maven 相依性外掛程式有助於管理和分析專案相依性。使用maven-dependency-plugin,
我們可以找到未使用的依賴項、顯示項目的依賴關係樹、尋找重複的依賴關係等等。
4.2. Maven 強制執行器插件
另一方面, maven-enforcer-plugin
允許我們在專案中強制執行規則和指南。該插件提供的選項之一是能夠禁止特定依賴項——這可以包括直接依賴項和傳遞依賴項。使用enforcer插件,我們還可以確保我們的專案沒有重複的依賴項。
4.3. Maven 幫助插件
由於 POM 可以繼承其他 POM 的配置,因此最終版本可以組合各種 POM。 maven-help-plugin
提供有關項目的資訊。為了獲得更多有助於識別配置問題的信息,我們可以使用插件的effective-pom
目標將有效的 POM 顯示為 XML。
5. 結論
在本文中,我們介紹了幾種用於改善專案中依賴項控制的技術。
頻繁更新、新增程式庫和維護現有程式庫包括確保所有相依性之間的相容性,這可能具有挑戰性。然而,現有的 Maven 功能有助於此過程。雖然各種插件可以自動化依賴管理並強製版本一致性,但記住 Maven 的解析規則很重要。
綜上所述,當遇到多個版本的依賴關係時,Maven首先透過依賴關係樹的深度來解決衝突。這裡,選擇最接近依賴樹根的定義。另一方面,如果兩個依賴項版本在樹中處於相同深度,Maven 將使用專案 POM 中第一個聲明的版本,並使其在最終建置中可用。
與往常一樣,帶有範例的完整原始程式碼可在 GitHub 上取得。