2016年12月23日 星期五

Maven BOM (bill of materials)

在比較大型的專案中使用 Maven,有頗高的機率會遇到版本衝突的問題
例如 project A 使用了 guava 的某個 15 版才開始有的功能,而 project B 則是引用了 guava v14
接著有一個新的專案 project C,必須要同時引用 project A 和 project B,這時會發生什麼事呢?
有可能會造成最後打包出來的專案,裡面實際上被打包的是 guava v14
因而導致了在執行時,執行到 project A 的程式碼,就遇到 class/method not found 之類的錯誤訊息。

在 Maven 中,針對這個問題有一些基礎的解法 [1]
當同樣的套件被重複引用時,Maven 的基礎規則是誰先寫就看誰
但畢竟在遇到某些動態載入之類的情境時,並不是那麼容易可以決定誰先寫
更何況遇到的如果是 guava 這種被大量引用的函式庫,其實我們不見得能夠清楚地知道到底有哪些函式庫用了它。
因此,在基礎的解法之上的下個階段,就是利用 dependencyManagement 來指派版本了。

具體來說,dependencyManagement 的指派好像有兩種用法,一種是建立父子關係,另一種則是 BOM
詳細的範例其實都可以參考官方文件 [1],這裡紀錄的是 BOM。

首先,假設我想要確保 guava 使用的版本是 v19.0,我可以先建立一個 BOM 專案,這個專案的 pom 檔內容如下:

<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>com.example</groupId>
    <artifactId>bom</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

    <name>bom</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>19.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

這裡需要特別注意的地方,大概是第 8 行的 packaging 必須是 pom
而 dependencyManagement 內,就寫上所有要預先指定版本的套件,這裡只寫了 guava v19。

接著,當我要使用的時候,也是以 dependencyManagement 的標籤來引用上面寫好的 com.example.bom 這個套件。

<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>com.example</groupId>
    <artifactId>project</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <name>project-a</name>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>bom</artifactId>
                <version>1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>
    </dependencies>
</project>

在這裡可以注意到兩個部份,一個是 com.example.bom 是以 pom type 被宣告在 dependencyManagement 中
另外,引用 guava 時,並沒有宣告 guava 的版本
這是因為 guava 的版本已經在 com.example.bom 裡被定義了,因此就算在這裡宣告了,也會被編譯器識別為不需要的內容。
在這樣的定義之下,就算 dependencies 裡面引入了其他會使用不同版本的 guava
也不會導致打包過程弄錯版本,進而更好地掌握打包的結果、避免版本衝突的議題產生。

參考資料
  1. Introduction to the Dependency Mechanism
  2. Maven实战(三)——多模块项目的POM重构 (许晓斌)

沒有留言: