聊聊Maven的依賴傳遞、依賴管理、依賴作用域

2023-10-12 12:01:03

1. 依賴傳遞

在Maven中,依賴是會傳遞的,假如在業務專案中引入了spring-boot-starter-web依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  	<version>2.7.4</version>
</dependency>

那麼業務專案不僅直接引入了spring-boot-starter-web依賴,還間接引入了spring-boot-starter-web的依賴項:

spring-boot-starterspring-boot-starter-jsonspring-boot-starter-tomcatspring-webspring-webmvc

Maven依賴關係如下圖所示:

外部庫如下圖所示:

其中,業務專案對spring-boot-starter-web的依賴稱為直接依賴,對spring-boot-starter-web的依賴項:

spring-boot-starterspring-boot-starter-jsonspring-boot-starter-tomcatspring-webspring-webmvc

的依賴稱為間接依賴。

2. 依賴管理

dependencyManagement元素主要用來統一管理依賴項的版本號。

假如父專案的pom檔案中宣告瞭如下依賴:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
</dependencyManagement>    

那麼子專案在新增該依賴時,可以不指定版本號:

<dependencies>
		<dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
    </dependency>
</dependencies>

Maven會自動找到父專案中宣告的該依賴項的版本號,如下圖所示:

這樣的優點是可以統一在父專案中管理依賴項的版本號,如果需要升級版本,只需改動父專案一個地方即可,子專案不用改動。

說明:

1)dependencyManagement只是宣告依賴項,並沒引入依賴項,子專案仍需顯式引入,不過可以不指定版本號

2)如果子專案不想使用繼承的父專案中的版本號,在子專案中指定版本號即可

3. 依賴作用域

在Maven中,可以使用scope來指定當前依賴項的作用域,常見的值有:compile、provided、runtime、test、import等,如下所示:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

3.1 compile

compile是預設的作用域,如果引入依賴時,沒有明確指定作用域,則依賴作用域為compile。

作用域為compile的依賴,在編譯、測試和執行時都是可用的,並且會參與專案的打包過程,該依賴會傳遞給依賴該模組的其他模組。

3.2 provided

作用域為provided的依賴,在編譯和測試時是可用的,在執行時是不可用的,不會參與專案的打包過程,也不會傳遞給其他模組。

比如lombok依賴會在編譯時生成相應的get、set等方法,在執行時就不需要這個依賴了,因此常常被指定為provided:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <scope>provided</scope>
</dependency>

因為被指定為provided,專案打包時是不包含lombok依賴項的,如下圖所示:

如果將上面的程式碼<scope>provided</scope>刪除的話,執行時是下圖這樣的:

以上驗證需將專案打包方式改為war,打包後檢視WEB-INF/lib目錄

3.3 runtime

作用域為runtime的依賴,在測試和執行時是可用的,在編譯時是不可用的,會參與專案的打包過程,也會傳遞給依賴該模組的

其他模組。

說明:

作用域為runtime的依賴中的類,在專案程式碼裡不能直接用,用了無法通過編譯(這裡指的是在src/main/java下使用)。

以mysql-connector-java為例,假如引入依賴時是下面這樣的:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

下面的範例程式碼是可以編譯通過的:

如果將作用域修改為runtime,上面的範例程式碼無法通過編譯:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
    <scope>runtime</scope>
</dependency>

3.4 test

作用域為test的依賴,只在測試時可用(包括測試程式碼的編譯、執行),不會參與專案的打包過程,也不會傳遞給其他模組。

常見的有junit、mockito等:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

因為被指定為test,專案打包時是不包含junit依賴項的,如下圖所示:

如果將上面的程式碼<scope>test</scope>刪除的話,執行時是下圖這樣的:

以上驗證需將專案打包方式改為war,打包後檢視WEB-INF/lib目錄

說明:

作用域為test的依賴中的類或者註解只能在src/test/java下才可以使用,在src/main/java下無法使用,如junit包下的@Test註解和org.junit.Assert斷言類。

3.5 import

每個專案,一般都會繼承自一個父專案,在實際的工作中,這個父專案一般都是公司架構組提供的帶有公司特色的一個基礎專案,

當然也可以是spring boot官方的專案。

以spring boot官方的專案為例:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.4</version>
</parent>

這個父專案中,會使用dependencyManagement標籤對依賴項的版本統一管理,子專案中,可以按需引入父專案

dependencyManagement中定義的依賴,但可以不指定版本號(版本號會自動繼承父專案中定義的版本號)。

但是存在以下2個問題:

  1. Maven是單繼承的,一個專案只能有一個parent專案
  2. parent專案dependencyManagement中的依賴項會越來越多,不好管理

依賴作用域import的出現就是為了解決以上問題,它可以通過非繼承的方式批次引入另一個依賴項中

dependencyManagement元素中定義的依賴項,如下所示:

<dependencyManagement>
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-bom</artifactId>
      <version>${spring-session-bom.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

說明:<scope>import</scope>只能用在dependencyManagement下type為pom的dependency中。

以上程式碼等價於新增了以下6個依賴項:

可以看出,使用<scope>import</scope>可以模組化的管理依賴項,提高複用性,pom檔案也更加簡潔。

3.6 區別

綜上所述,各個依賴作用域的區別如下表所示:

scope取值 編譯時可用 測試時可用 執行時可用 是否參與打包 依賴傳遞
compile
provided × × ×
runtime ×
test × × × ×

4. 影響依賴傳遞的因素

4.1 依賴作用域(scope)

依賴作用域會影響依賴傳遞,從上表可以看出,如果scope為provided或者test,該依賴不會傳遞,只有scope為compile或者runtime,

該依賴才會傳遞。

4.2 可選依賴(optional)

通過dependency標籤引入依賴時,可以通過<optional>指定該依賴是不是可選的,預設值為false:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
	  <version>3.2.3</version>
    <optional>true</optional>
</dependency>

如果<optional>值為true,那麼這個依賴不會傳遞。