1. 程式人生 > 其它 >Maven依賴傳遞,排除依賴和可選依賴

Maven依賴傳遞,排除依賴和可選依賴

Maven 依賴傳遞是 Maven 的核心機制之一,它能夠一定程度上簡化 Maven 的依賴配置。本節我們將詳細介紹依賴傳遞及其相關概念。

依賴傳遞

如下圖所示,專案 A 依賴於專案 B,B 又依賴於專案 C,此時 B 是 A 的直接依賴,C 是 A 的間接依賴。

Maven 的依賴傳遞機制是指:不管 Maven 專案存在多少間接依賴,POM 中都只需要定義其直接依賴,不必定義任何間接依賴,Maven 會動讀取當前專案各個直接依賴的 POM,將那些必要的間接依賴以傳遞性依賴的形式引入到當前專案中。Maven 的依賴傳遞機制能夠幫助使用者一定程度上簡化 POM 的配置。


基於 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 中,但這種引入並不是無條件的,它會受到依賴範圍的影響。

傳遞性依賴的依賴範圍受第一直接依賴和第二直接依賴的範圍影響,如下表所示。

compiletestprovidedruntime
compile compile - - runtime
test test - - test
provided provided - provided provided
runtime runtime - - runtime


注:上表中,左邊第一列表示第一直接依賴的依賴範圍,上邊第一行表示第二直接依賴的依賴範圍。交叉部分的單元格的取值為傳遞性依賴的依賴範圍,若交叉單元格取值為“-”,則表示該傳遞性依賴不能被傳遞

通過上表,可以總結出以下規律(*):

  • 當第二直接依賴的範圍是 compile 時,傳遞性依賴的範圍與第一直接依賴的範圍一致;
  • 當第二直接依賴的範圍是 test 時,傳遞性依賴不會被傳遞;
  • 當第二直接依賴的範圍是 provided 時,只傳遞第一直接依賴的範圍也為 provided 的依賴,且傳遞性依賴的範圍也為 provided;
  • 當第二直接依賴的範圍是 runtime 時,傳遞性依賴的範圍與第一直接依賴的範圍一致,但 compile 例外,此時傳遞性依賴的範圍為 runtime。

依賴調節

Maven 的依賴傳遞機制可以簡化依賴的宣告,使用者只需要關心專案的直接依賴,而不必關心這些直接依賴會引入哪些間接依賴。但當一個間接依賴存在多條引入路徑時,為了避免出現依賴重複的問題,Maven 通過依賴調節來確定間接依賴的引入路徑。

依賴調節遵循以下兩條原則:

  1. 引入路徑短者優先
  2. 先宣告者優先


以上兩條原則,優先使用第一條原則解決,第一條原則無法解決,再使用第二條原則解決。

引入路徑短者優先

引入路徑短者優先,顧名思義,當一個間接依賴存在多條引入路徑時,引入路徑短的會被解析使用。

例如,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,否則排除依賴無法生效。