iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Software Development

我們與Maven的距離系列 第 14

Day13 - Transitive Dependencies

  • 分享至 

  • xImage
  •  

前言

基於工程師不重複造輪子的原則,我們在開發專案時經常會引入各種第三方套件來提升開發效率。但試想一個情況:當您引入的套件 A 依賴於套件 B,而套件 B 又依賴於套件 C、D、E...,如果需要手動逐一下載和管理這些依賴關係,這將是一個苦力活!更不用說還要確保版本相容性了。

另一個常見的挑戰是依賴衝突:當套件 A 使用 Logback 1.5.18 版本,而套件 B 卻使用 Logback 1.5.0 版本時,應該如何選擇?

幸好,Maven 的傳遞依賴機制為我們解決了這兩大難題。

依賴傳遞

當我們在pom.xml設定依賴某個套件,執行Maven時會自動下載該依賴所需要的其他依賴,這些間接依賴就叫做「傳遞依賴」,例如:我們設定了

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.30.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.6</version>
</dependency>

我們可以執行mvn dependency:tree來查看套件相依的行為,就會發現你的org.springframework依賴關係的樣子
https://ithelp.ithome.com.tw/upload/images/20250928/20128084WQg04e6Axx.png

依賴傳遞原則

1. 最短路徑優先(Nearest Definition)

當同一個依賴有多個版本時,選擇依賴路徑最短的版本。
範例:

專案 A
├─ B 
│  └─ E 2.0
└─ C 
   └─ D
      └─ E 1.0 

在這個例子中,E 有兩個版本:

  • A → B → E (2.0) 路徑長度 = 2
  • A → C → D → E (1.0) 路徑長度 = 3
    所以最終使用的套件版本會是2.0的版本
    範例:
    我們在pom.xml,在spring的下方設定加入esapi
<dependency>
    <groupId>org.owasp.esapi</groupId>
    <artifactId>esapi</artifactId>
    <version>2.7.0.0</version>
</dependency>

這次我們的指令加入-Dverbose參數,使用mvn dependency:tree -Dverbose,會發現被因路徑長度被捨棄使用esapi相依的commons-logging:1.3.5
https://ithelp.ithome.com.tw/upload/images/20250928/20128084w00DkjF4Us.png

2. 聲明優先(First Declaration)

當路徑長度相同時,使用第一個聲明的版本。

範例:
我們在pom.xml把esapi移除在spring下方加入bean util,來測試路徑長度一樣時的狀況

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.6</version>
</dependency>

執行mvn dependency:tree -Dverbose發現到會採用spring的commons-logging:1.2
https://ithelp.ithome.com.tw/upload/images/20250928/20128084Ar0jYY55jw.png
調換兩個聲明順序,執行mvn dependency:tree -Dverbose,則會使用beanUtil的commons-logging:1.0
https://ithelp.ithome.com.tw/upload/images/20250928/20128084C1dCJPa3IG.png

依賴傳遞與依賴範圍 (Transitive Dependencies and Dependency Scope)

什麼是依賴範圍的傳遞?

當 A 依賴 B,B 又依賴 C 時,C 最終在 A 專案中會是什麼範圍?這就是依賴範圍傳遞的核心問題。

依賴範圍傳遞規則表

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

表格說明:

  • 第一欄:您在 pom.xml 中直接宣告的依賴範圍 (A→B)
  • 第一列:B 套件對 C 套件的依賴範圍 (B→C)
  • 交叉格:最終 C 套件在您專案中的依賴範圍 (A→C)
  • 符號 "-":表示該依賴不會被傳遞到您的專案中

實際範例理解

說明 1:compile → compile = compile

我離不開你,你離不開他,那麼我也沒辦法離開你的概念

說明 2:compile → runtime = runtime

<!-- 您直接依賴某個套件 (compile) -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>XXX-library</artifactId>
    <version>1.0</version>
    <scope>compile</scope>  <!-- A→B: compile -->
</dependency>

如果 XXX-library 內部依賴某個 database driver (runtime scope):

  • A → B: compile
  • B → C (database driver): runtime
  • 結果: A → C: runtime

說明 3:compile → provided = - (不傳遞)

<!-- Web 應用依賴 Spring Web MVC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.21</version>
    <scope>compile</scope>  <!-- A→B: compile -->
</dependency>

如果 spring-webmvc 內部依賴 servlet-api (provided scope):

  • A → B: compile
  • B → C (servlet-api): provided
  • 結果: A → C: - (不會傳遞) 因為容器會提供 servlet-api

情境題1.公司都使用Spring Boot不過你可以幫我包一個war部署到JBoss上面嗎XDD

在某些公司習慣使用有大公司Support的Application Server,所以就會產生你用Spring Boot開發但是打包時需要移除它embeded tommcat,我們先透過Spring Initializr創建一個Spring Boot Web專案並加入以下設定,對spring-boot-starter-tomcat設定依賴範圍為provided,如此一來embeded tommcat就不會被打包

<packaging>war</packaging>

<dependencies>
  <!-- 略 -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
  </dependency>
</dependencies>

執行mvn package,於target目錄下產生WAR檔

## 小結
今日說明了依賴傳遞的原則,與依賴範圍傳遞的關係,知道這些關係原則後如果要改變它該如何做呢?這是我們明天的課題

Reference


上一篇
Day12 - Dependency Scope
下一篇
Day14 - Dependency Management
系列文
我們與Maven的距離16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言