1. 程式人生 > >Maven 解決jar包衝突的原理

Maven 解決jar包衝突的原理

管理包依賴是 Maven 核心功能之一,下面通過如何引入 jar 包;如何解析 jar 包依賴;包衝突是如何產生;如何解決包衝突;依賴管理解決什麼問題;什麼是依賴範圍;使用包依賴的最佳實踐等 6 個問題來介紹。

如何引入 jar 包

在程式碼開發時,如果需要使用第三方 jar 包提供的類庫,那麼需要在 pom.xml 加入該 jar 包依賴。 例如:使用 zookeeper client

<dependencies>
  <!-- https://mvnrepository.com/artifact/org.apache.hadoop/zookeeper -->
  <dependency
>
<groupId>org.apache.hadoop</groupId> <artifactId>zookeeper</artifactId> <version>3.3.1</version> </dependency> </dependencies>

Maven 如何解析 jar 包依賴——傳遞依賴

如上所述,在 pom.xml 中引入 zookeeper jar 包依賴,當 Maven 解析該依賴時,需要引入的 jar 包不僅僅只有 zookeeper,還會有 zookeeper 內部依賴的 jar 包,還會有 zookeeper 內部依賴的 jar 包依賴的 jar 包......,依賴關係不斷傳遞,直至沒有依賴。
例如:上述 pom.xml 引入 zookeeper 依賴,實際引入的 jar 包有

包衝突如何產生?

舉個�:假設 A->B->C->D1, E->F->D2,D1,D2 分別為 D 的不同版本。
如果 pom.xml 檔案中引入了 A 和 E 之後,按照 Maven 傳遞依賴原則,工程內需要引入的實際 Jar 包將會有:A B C D1 和 E F D2,因此 D1,D2 將會產生包衝突。

如何解決包衝突

Maven 解析 pom.xml 檔案時,同一個 jar 包只會保留一個,這樣有效的避免因引入兩個 jar 包導致的工程執行不穩定性。

Maven 預設處理策略

  • 最短路徑優先
    Maven 面對 D1 和 D2 時,會預設選擇最短路徑的那個 jar 包,即 D2。E->F->D2 比 A->B->C->D1 路徑短 1。
  • 最先宣告優先
    如果路徑一樣的話,舉個�: A->B->C1, E->F->C2 ,兩個依賴路徑長度都是 2,那麼就選擇最先宣告。

移除依賴

如果我們不想通過 A->B->->D1 引入 D1 的話,那麼我們在宣告引入 A 的時候將 D1 排除掉,這樣也避免了包衝突。
舉個�:將 zookeeper 的 jline 依賴排除

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.3.1</version>
    <exclusions>
        <exclusion>
            <groupId>jline</groupId>
            <artifactId>jline</artifactId>
        </exclusion>
    </exclusions>
</dependency>

檢測包衝突工具

mvn dependency:help
mvn dependency:analyze
mvn dependency:tree
mvn dependency:tree -Dverbose

依賴管理解決什麼問題

當同一個工程內有多個模組時,並且要求多個模組使用某個 jar 包的相同版本,為了方便統一版本號,升級版本號,需要提取出一個父親模組來管理子模組共同依賴的 jar 包版本。
舉個�:有兩個模組 projectA, projectB,它們的依賴分別如下所示:
projectA:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

projectB:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>bar</type>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

projectA 和 projectB 共同依賴了 group-a/artifact-b/1.0,提取公共依賴,生成 parent, parent 依賴如下:

<project>
  ...
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>group-a</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
        <type>bar</type>
        <scope>runtime</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

則 projectA 和 projectB 均不需要指定 group-a/artifact-b 的 version 資訊,未來升級 version 資訊時,只需要在 parent 內部指定。

projectA:

<project>
  ...
<parent>
<groupId>group-a</groupId>
        <artifactId>artifact-b</artifactId>
        <version>1.0</version>
</parent>
  <dependencies>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-a</artifactId>
      <version>1.0</version>
      <exclusions>
        <exclusion>
          <groupId>group-c</groupId>
          <artifactId>excluded-artifact</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
    </dependency>
  </dependencies>
</project>

projectB:

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>group-c</groupId>
      <artifactId>artifact-b</artifactId>
      <version>1.0</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>group-a</groupId>
      <artifactId>artifact-b</artifactId>
    </dependency>
  </dependencies>
</project>

依賴範圍

如果不顯示執行 <scope> 屬性時,預設 <scope>compile</scope>。
scope 有哪些屬性:compile, provided, runtime, test, system 等。
詳細參考:依賴範圍

最佳實踐

(1)專案中原始碼使用的 jar 包一定在 pom.xml 中顯示引用。
(2)經常 check 一下包衝突,檢查是否需要處理。
(3)當使用多個模組時,parent 一定要使用包管理模組來規範 Jar 包版本,而不是包依賴模組直接引入依賴。 dependencyManagement vs dependencies