Maven依賴機制

布禾卡斐先生發表於2019-05-26

依賴傳遞

依賴相關命令

mvn dependency:list:檢視當前專案所有依賴。
mvn dependency:tree:以樹的形式顯示當前專案的所有依賴,相比mvn dependency:list 列表顯示,能很清楚的看到某個依賴是通過哪條依賴路徑引入的。
mvn dependency:analyze:分析專案的依賴關係,並確定哪些依賴是:使用和宣告、使用和未宣告、未使用和宣告。

依賴的傳遞性

如有依賴關係為A->B->C,A依賴B,稱為直接依賴。A本身不依賴C,但C通過B傳遞給A,稱C為A的傳遞性依賴
Maven依賴機制

通過mvn dependency:list檢視A專案的依賴列表,可以看到依賴B和C:

    [INFO] --- maven-dependency-plugin:2.8:list (default-cli) @ A --- 
    [INFO] 
    [INFO] The following files have been resolved:
    [INFO]    com.nocoffee:B:jar:0.0.1-SNAPSHOT:compile
    [INFO]    junit:junit:jar:3.8.1:test
    [INFO]    com.nocoffee:C:jar:0.0.1-SNAPSHOT:compile
    [INFO] 
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS 
    [INFO] ------------------------------------------------------------------------

依賴調解

場景1:

路徑1:A->B->C(version:1.0)
路徑2:A->D->E->C(version:2.0)
通過兩條依賴路徑可以看出,A的傳遞性依賴的C有兩個不同版本,為了避免依賴重複,最終只能選擇一個。這種情況Maven採用路徑最近者優先的原則來處理,路徑1中C到A的距離比路徑2中C到A的距離要短,於是路徑1中C(version:1.0)最終被A依賴。

Maven依賴機制

mvn dependency:tree 檢視依賴路徑:

    [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
    [INFO] com.nocoffee:A:jar:0.0.1-SNAPSHOT
    [INFO] +- com.nocoffee:B:jar:0.0.1-SNAPSHOT:compile
    [INFO] |  \- com.nocoffee:C:jar:0.0.1-SNAPSHOT:compile
    [INFO] |     \- com.nocoffee:D:jar:0.0.1-SNAPSHOT:compile
    [INFO] |        \- com.nocoffee:E:jar:0.0.1-SNAPSHOT:compile
    [INFO] \- junit:junit:jar:3.8.1:test
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

場景2:

路徑1:A->B->C(version:1.0)
路徑2:A->D->C(version:2.0)
路徑1和路徑2中C到A的距離是相同的,通過路徑最近者優先原則無法判斷該使用哪個依賴,此時Maven會使用第一宣告者優先原則進行選擇,第一宣告者優先原則是指在POM依賴中宣告順序最靠前的那個依賴會被選擇。在A的POM檔案中B的宣告靠前,於是C(version:1.0)會被選擇。

Maven依賴機制

    <!-- A的pom.xml中依賴部分-->
    <dependencies>
    <dependency>
      <groupId>com.nocoffee</groupId>
      <artifactId>B</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>com.nocoffee</groupId>
      <artifactId>D</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

mvn dependency:tree 檢視依賴路徑:

    [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
    [INFO] com.nocoffee:A:jar:0.0.1-SNAPSHOT
    [INFO] +- com.nocoffee:B:jar:0.0.1-SNAPSHOT:compile
    [INFO] |  \- com.nocoffee:C:jar:0.0.1-SNAPSHOT:compile
    [INFO] +- com.nocoffee:D:jar:0.0.1-SNAPSHOT:compile
    [INFO] \- junit:junit:jar:3.8.1:test
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

排除依賴

在場景2中,如果要使A依賴C(version:2.0) ,則可以配置排除依賴:


  <dependencies>
    <dependency>
      <groupId>com.nocoffee</groupId>
      <artifactId>B</artifactId>
      <version>0.0.1-SNAPSHOT</version>

      <exclusions>
        <!-- 排除依賴 C -->
        <exclusion>
            <groupId>com.nocoffee</groupId>
            <artifactId>C</artifactId>
        </exclusion>
      </exclusions>

    </dependency>
    <dependency>
      <groupId>com.nocoffee</groupId>
      <artifactId>D</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

mvn dependency:tree 檢視依賴路徑,A不再通過B依賴C,而是通過D依賴C:

    [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
    [INFO] com.nocoffee:A:jar:0.0.1-SNAPSHOT
    [INFO] +- com.nocoffee:B:jar:0.0.1-SNAPSHOT:compile
    [INFO] +- com.nocoffee:D:jar:0.0.1-SNAPSHOT:compile
    [INFO] |  \- com.nocoffee:C:jar:0.0.1-SNAPSHOT:compile
    [INFO] \- junit:junit:jar:3.8.1:test
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

可選依賴

可以將某個依賴配置為可選依賴,則該依賴不會參與依賴傳遞。

以場景2為例,可以在B的pom.xml裡將C配置為可選依賴,使A依賴D的C(version:2.0)。

    <!-- B的pom.xml -->
    <dependency>
      <groupId>com.nocoffee</groupId>
      <artifactId>C</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <!-- 設定可選依賴 -->
      <optional>true</optional>
    </dependency>

mvn dependency:tree 檢視依賴路徑,A不再通過B依賴C,而是通過D依賴C:

    [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ A ---
    [INFO] com.nocoffee:A:jar:0.0.1-SNAPSHOT
    [INFO] +- com.nocoffee:B:jar:0.0.1-SNAPSHOT:compile
    [INFO] +- com.nocoffee:D:jar:0.0.1-SNAPSHOT:compile
    [INFO] |  \- com.nocoffee:C:jar:0.0.1-SNAPSHOT:compile
    [INFO] \- junit:junit:jar:3.8.1:test
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

依賴範圍

Maven在編譯、測試、執行時都會使用不同的classpath,依賴範圍是用來控制依賴和三種classpath的關係。

依賴範圍介紹

  1. compile:編譯依賴範圍,預設使用該依賴範圍,在所有classpath中都可用,並且依賴項將傳播到依賴專案。
  2. provided:已提供依賴範圍,只對於編譯和測試classpath有效,執行時無效,如Servlet API,此範圍不具有傳遞性。
  3. runtime:執行時依賴範圍,只對於測試和執行classpath有效,但在編譯主程式碼時無效。
  4. test:測試依賴範圍,只對於測試的classpath有效,僅適用於測試編譯和執行階段,如junit。此範圍不具有傳遞性。
  5. system:系統依賴範圍,該依賴於三種classpath的關係和provided依賴範圍完全一致。區別在於system依賴範圍必須通過systemPath元素顯示的指定依賴檔案的路徑。
  6. import:匯入依賴範圍,該依賴範圍不會對三種classpath產生影響,只有在部分中的pom型別依賴項才支援此範圍,它指示要替換為指定POM的部分中的有效依賴項列表的依賴項。由於它們被替換,具有匯入範圍的依賴項實際上不參與限制依賴項的傳遞性。

依賴範圍對依賴傳遞的影響

每個範圍(import匯入依賴範圍除外)以不同方式影響傳遞依賴性,如下表所示。以A->B->C依賴路徑為例,左邊第一列為第一直接依賴(B在A中的依賴範圍),最上面一行為第二直接依賴(C在B中的依賴範圍),交叉單元格為傳遞性依賴範圍(C在A中的依賴範圍)。

compile provided runtime test
compile compile(*) - runtime -
provided provided - provided -
runtime runtime - runtime -
test test - test -

相關文章