Maven依賴傳遞,排除依賴和可選依賴
Maven 依賴傳遞是 Maven 的核心機制之一,它能夠一定程度上簡化 Maven 的依賴配置。本節我們將詳細介紹依賴傳遞及其相關概念。
依賴傳遞
如下圖所示,專案 A 依賴於專案 B,B 又依賴於專案 C,此時 B 是 A 的直接依賴,C 是 A 的間接依賴。
基於 A、B、C 三者的依賴關係,根據 Maven 的依賴傳遞機制,我們只需要在專案 A 的 POM 中定義其直接依賴 B,在專案 B 的 POM 中定義其直接依賴 C,Maven 會解析 A 的直接依賴 B的 POM ,將間接依賴 C 以傳遞性依賴的形式引入到專案 A 中。
通過這種依賴傳遞關係,可以使依賴關係樹迅速增長到一個很大的量級,很有可能會出現依賴重複,依賴衝突等情況,Maven 針對這些情況提供瞭如下功能進行處理。
- 依賴範圍(Dependency scope)
- 依賴調解(Dependency mediation)
- 可選依賴(Optional dependencies)
- 排除依賴(Excluded dependencies)
- 依賴管理(Dependency management)
依賴範圍
首先,我們要知道 Maven 在對專案進行編譯、測試和執行時,會分別使用三套不同的 classpath。Maven 專案構建時,在不同階段引入到 classpath 中的依賴時不同的。例如編譯時,Maven 會將與編譯相關的依賴引入到編譯 classpath 中;測試時,Maven 會將與測試相關的的依賴引入到測試 classpath 中;執行時,Maven 會將與執行相關的依賴引入到執行 classpath 中。
我們可以在 POM 的依賴宣告使用 scope 元素來控制依賴與三種 classpath(編譯 classpath、測試 classpath、執行 classpath )之間的關係,這就是依賴範圍
Maven 具有以下 6 中常見的依賴範圍,如下表所示。
依賴範圍 | 描述 |
---|---|
compile | 編譯依賴範圍,scope 元素的預設值。使用此依賴範圍的 Maven 依賴,對於三種 classpath 均有效,即該 Maven 依賴在上述三種 classpath 均會被引入。例如,log4j 在編譯、測試、執行過程都是必須的。 |
test | 測試依賴範圍。使用此依賴範圍的 Maven 依賴,只對測試 classpath 有效。例如,Junit 依賴只有在測試階段才需要。 |
provided | 已提供依賴範圍。使用此依賴範圍的 Maven 依賴,只對編譯 classpath 和測試 classpath 有效。例如,servlet-api 依賴對於編譯、測試階段而言是需要的,但是執行階段,由於外部容器已經提供,故不需要 Maven 重複引入該依賴 |
runtime | 執行時依賴範圍。使用此依賴範圍的 Maven 依賴,只對測試 classpath、執行 classpath 有效。例如,JDBC 驅動實現依賴,其在編譯時只需 JDK 提供的 JDBC 介面即可,只有測試、執行階段才需要實現了 JDBC 介面的驅動。 |
system | 系統依賴範圍,其效果與 provided 的依賴範圍一致。其用於新增非 Maven 倉庫的本地依賴,通過依賴元素 dependency 中的 systemPath 元素指定本地依賴的路徑。鑑於使用其會導致專案的可移植性降低,一般不推薦使用。 |
import | 匯入依賴範圍,該依賴範圍只能與 dependencyManagement 元素配合使用,其功能是將目標 pom.xml檔案中 dependencyManagement 的配置匯入合併到當前 pom.xml 的 dependencyManagement 中。 |
依賴範圍與三種 classpath 的關係一覽表,如下所示。
依賴範圍 | 編譯 classpath | 測試 classpath | 執行classpath | 例子 |
---|---|---|---|---|
compile | √ | √ | √ | log4j |
test | - | √ | - | junit |
provided | √ | √ | - | servlet-api |
runtime | - | - | √ | JDBC-driver |
system | √ | √ | - | 非 Maven 倉庫的本地依賴 |
依賴範圍對傳遞依賴的影響
專案 A 依賴於專案 B,B 又依賴於專案 C,此時我們可以將 A 對於 B 的依賴稱之為第一直接依賴,B 對於 C 的依賴稱之為第二直接依賴。
B 是 A 的直接依賴,C 是 A 的間接依賴,根據 Maven 的依賴傳遞機制,間接依賴 C 會以傳遞性依賴的形式引入到 A 中,但這種引入並不是無條件的,它會受到依賴範圍的影響。
傳遞性依賴的依賴範圍受第一直接依賴和第二直接依賴的範圍影響,如下表所示。
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
注:上表中,左邊第一列表示第一直接依賴的依賴範圍,上邊第一行表示第二直接依賴的依賴範圍。交叉部分的單元格的取值為傳遞性依賴的依賴範圍,若交叉單元格取值為“-”,則表示該傳遞性依賴不能被傳遞。
通過上表,可以總結出以下規律(*):
- 當第二直接依賴的範圍是 compile 時,傳遞性依賴的範圍與第一直接依賴的範圍一致;
- 當第二直接依賴的範圍是 test 時,傳遞性依賴不會被傳遞;
- 當第二直接依賴的範圍是 provided 時,只傳遞第一直接依賴的範圍也為 provided 的依賴,且傳遞性依賴的範圍也為 provided;
- 當第二直接依賴的範圍是 runtime 時,傳遞性依賴的範圍與第一直接依賴的範圍一致,但 compile 例外,此時傳遞性依賴的範圍為 runtime。
依賴調節
Maven 的依賴傳遞機制可以簡化依賴的宣告,使用者只需要關心專案的直接依賴,而不必關心這些直接依賴會引入哪些間接依賴。但當一個間接依賴存在多條引入路徑時,為了避免出現依賴重複的問題,Maven 通過依賴調節來確定間接依賴的引入路徑。
依賴調節遵循以下兩條原則:
- 引入路徑短者優先
- 先宣告者優先
以上兩條原則,優先使用第一條原則解決,第一條原則無法解決,再使用第二條原則解決。
引入路徑短者優先
引入路徑短者優先,顧名思義,當一個間接依賴存在多條引入路徑時,引入路徑短的會被解析使用。
例如,A 存在這樣的依賴關係:
A->B->C->D(1.0)
A->X->D(2.0)
D 是 A 的間接依賴,但兩條引入路徑上有兩個不同的版本,很顯然不能同時引入,否則造成重複依賴的問題。根據 Maven 依賴調節的第一個原則:引入路徑短者優先,D(1.0)的路徑長度為 3,D(2.0)的路徑長度為 2,因此間接依賴 D(2.0)將從 A->X->D(2.0) 路徑引入到 A 中。
先宣告者優先
先宣告者優先,顧名思義,在引入路徑長度相同的前提下,POM 檔案中依賴宣告的順序決定了間接依賴會不會被解析使用,順序靠前的優先使用。
例如,A 存在以下依賴關係:
A->B->D(1.0)
A->X->D(2.0)
D 是 A 的間接依賴,其兩條引入路徑的長度都是 2,此時 Maven 依賴調節的第一原則已經無法解決,需要使用第二原則:先宣告者優先。
A 的 POM 檔案中配置如下。
<dependencies> ... <dependency> ... <artifactId>B</artifactId> ... </dependency> ... <dependency> ... <artifactId>X</artifactId> ... </dependency> ... </dependencies>
有以上配置可以看出,由於 B 的依賴宣告比 X 靠前,所以間接依賴 D(1.0)將從A->B->D(1.0) 路徑引入到 A 中。
Maven排除依賴和可選依賴
我們知道 Maven 依賴具有傳遞性,例如 A 依賴於 B,B 依賴於 C,在不考慮依賴範圍等因素的情況下,Maven 會根據依賴傳遞機制,將間接依賴 C 引入到 A 中。但如果 A 出於某種原因,希望將間接依賴 C 排除,那該怎麼辦呢?Maven 為使用者提供了兩種解決方式:排除依賴(Dependency Exclusions)和可選依賴(Optional Dependencies)。
排除依賴
假設存在這樣的依賴關係,A 依賴於 B,B 依賴於 X,B 又依賴於 Y。B 實現了兩個特性,其中一個特性依賴於 X,另一個特性依賴於 Y,且兩個特性是互斥的關係,使用者無法同時使用兩個特性,所以 A 需要排除 X,此時就可以在 A 中將間接依賴 X 排除。
排除依賴是通過在 A 中使用 exclusions 元素實現的,該元素下可以包含若干個exclusion 子元素,用於排除若干個間接依賴,示例程式碼如下。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.biancheng.www</groupId> <artifactId>A</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>net.biancheng.www</groupId> <artifactId>B</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <!-- 設定排除 --> <!-- 排除依賴必須基於直接依賴中的間接依賴設定為可以依賴為 false --> <!-- 設定當前依賴中是否使用間接依賴 --> <exclusion> <!--設定具體排除--> <groupId>net.biancheng.www</groupId> <artifactId>X</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </project>
關於exclusions 元素及排除依賴說明如下:
- 排除依賴是控制當前專案是否使用其直接依賴傳遞下來的間接依賴;
- exclusions 元素下可以包含若干個 exclusion 子元素,用於排除若干個間接依賴;
- exclusion 元素用來設定具體排除的間接依賴,該元素包含兩個子元素:groupId 和artifactId,用來確定需要排除的間接依賴的座標資訊;
- exclusion 元素中只需要設定groupId 和artifactId 就可以確定需要排除的依賴,無需指定版本 version。
可選依賴
與上文的應用場景相同,也是 A 希望排除間接依賴 X,除了在 B 中設定可選依賴外,我們還可以在B 中將 X 設定為可選依賴。
設定可選依賴
在 B 的 POM 關於 X 的依賴宣告中使用 optional 元素,將其設定成可選依賴,示例配置如下。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.biancheng.www</groupId> <artifactId>B</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>net.biancheng.www</groupId> <artifactId>X</artifactId> <version>1.0-SNAPSHOT</version> <!--設定可選依賴 --> <optional>true</optional> </dependency> </dependencies> </project>
關於 optional 元素及可選依賴說明如下:
- 可選依賴用來控制當前依賴是否向下傳遞成為間接依賴;
- optional 預設值為 false,表示可以向下傳遞稱為間接依賴;
- 若 optional 元素取值為 true,則表示當前依賴不能向下傳遞成為間接依賴。
排除依賴 VS 可選依賴
排除依賴和可選依賴都能在專案中將間接依賴排除在外,但兩者實現機制卻完全不一樣。
- 排除依賴是控制當前專案是否使用其直接依賴傳遞下來的接間依賴;
- 可選依賴是控制當前專案的依賴是否向下傳遞;
- 可選依賴的優先順序高於排除依賴;
- 若對於同一個間接依賴同時使用排除依賴和可選依賴進行設定,那麼可選依賴的取值必須為 false,否則排除依賴無法生效。