1. 程式人生 > >Maven:繼承。

Maven:繼承。

面向物件設計中,程式設計師可以建立一種類的父子結構,然後在父類中宣告一些欄位和方法供子類繼承,這樣就可以做到“一處宣告,多處使用”。類似的,我們需要建立POM的父子結構,然後在父POM中宣告一些配置供子POM繼承,以實現“一處宣告,多處使用”的目的。

我們在模組a下建立一個名為b的子目錄,然後在該子目錄下建立一個所有除a之外模組的父模組。為此,在該子目錄建立一個pom.xml檔案,內容如下。

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>b</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>b</name>
</project>

該POM十分簡單,他使用了與其他模組一致的groupId和version,使用的artifactId為b表示這時一個父模組。需要特別注意的是,他的packaging為pom,這一點與聚合模組一樣,作為父模組的POM,其打包型別頁必須為pom。

由於父模組只是為了幫助消除配置的重複,因此他本身不包含除POM之外的專案檔案,也就不需要src/main/java/之類的檔案夾了。

有了父模組,就需要讓其他模組來繼承他。首先將c的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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.test</groupId>
        <artifactId>b</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../b/pom.xml</relativePath>
    </parent>
    <artifactId>c</artifactId>
    <name>c</name>
    <dependencies>
        ...
    </dependencies>
    <build>
        <plugins>
            ...
        </plugins>
    </build>
</project>

上述POM中使用parent元素宣告父模組,parent下的子元素groupId、artifactId和version制定了父模組的座標,這三個元素是必須的。元素relativePath表示父模組POM的相對路徑,該例中的../b/pom.xml表示父POM的位置在與c/目錄平行的a/目錄下。當專案構建時,Maven會首先根據relativePath檢查父POM,如果找不到,再從本地倉庫查詢。relativePath的預設值../pom.xml,也就是說,Maven預設父POM在上一層目錄下。

正確設定relativePath非常重要。考慮這樣一個情況,開發團隊的新成員從原始碼庫簽出一個包含父子模組關係的Maven專案。由於只關心其中的某一個子模組,他就直接到該模組的目錄下執行構建,這個時候,父模組是沒有被安裝到本地倉庫的,因此如果子模組沒有設定正確的relativePath,Maven將無法找到父POM,這將直接導致構建失敗。如果Maven能夠根據relativePath找到父POM,他就不需要再去檢查本地倉庫。

這個更新過的POM沒有為account-email宣告groupId和version,不過這並不代表c沒有groupId和version。實際上,這個子模組隱式的從父模組繼承了這兩個元素,這也就消除了一些不必要的配置。在該例中,父子模組使用同樣的groupId和version,如果遇到子模組需要使用和父模組不一樣的groupId或者version的情況,那麼使用者完全可以在子模組中顯式宣告。對於artifactId元素來說,子模組應該顯式宣告,一方面,如果完全繼承groupId、artifactId和version,會造成座標衝突;另一方面,即使使用不同的groupId或version,同樣的artifactId容易造成混淆。 最後,同樣還需要把a加入到聚合模組d中,見下面。

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>d</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>d</name>
    <modules>
        <module>a</module>
        <module>b</module>
        <module>c</module>
    </modules>

</project>

可繼承的POM元素

在上面我們看到,groupId和version是可以被繼承的,那麼還有哪些POM元素可以被繼承呢?以下是一個完整的列表,並附帶了簡單的說明:

  • groupId:專案組ID,專案座標的核心元素。
  • version:專案版本,專案座標的核心元素。
  • description:專案的描述資訊。
  • organization:專案的組織資訊。
  • inceptionYear:專案的創始年份。
  • url:專案的URL地址。
  • developers:專案的開發者資訊。
  • contributors:專案的開發者資訊。
  • distributionManagement:專案的部署配置。
  • issueManagement:專案的缺陷跟蹤系統資訊。
  • ciManagement:專案的持續整合系統資訊。
  • scm:專案的版本控制系統資訊。
  • mailingLists:專案的郵件列表資訊。
  • properties:自定義的Maven屬性。
  • dependencies:專案的依賴配置。
  • dependencyManagement:專案的依賴管理配置。
  • repositories:專案的倉庫配置。
  • build:包括專案的原始碼目錄配置:輸出目錄配置、外掛配置、外掛管理配置等。
  • reporting:包括專案的報告輸出目錄配置、報告外掛配置等。

依賴管理

Maven提供的dependencyManagement元素既能讓子模組整合到父模組的依賴配置,又能保證子模組依賴使用的靈活性。在dependencyManagement元素下的依賴宣告不會引入實際的依賴,不過他能夠約束dependencies下的依賴使用。例如,可以在a中加入這樣的dependencyManagement配置,見下面所示。

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.test</groupId>
    <artifactId>a</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>a</name>
    <properties>
        <springframework.version>2.5.6</springframework.version>
        <junit.version>4.7</junit.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>junit<groupId>
                <artifactId>junit</artifactId>
                <version>{junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

首先將springframework和junit依賴的版本以Maven變數的形式提取了出來,不僅消除了一些重複,也使得各依賴的版本處於更加明顯的位置。

這裡使用dependencyManagement宣告的依賴既不會給a引入依賴,也不會給他的子模組引入依賴,不過這段配置是會被繼承的。現在修改b的POM如下所示。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
</dependencies>

上述POM中的依賴配置較原來簡單了一些,所有的springframework依賴只配置了groupId和artifactId,省去了version,而junit依賴不僅省去了version,還省去了依賴範圍scope。這些資訊可以省略是因為b繼承了a中的dependencyManagement配置,完整的依賴宣告已經包含在父POM中,子模組只需要配置簡單地groupId和artifactId就能獲得對應的依賴資訊,從而引入正確的依賴。

使用這種依賴管理機制似乎不能減少太多的POM配置,不過還是強烈推薦採用這種方法。其主要原因在於在父POm中使用dependencyManagement宣告依賴能夠統一專案範圍中依賴的版本,當依賴本版在父POM中生命之後,子模組在使用依賴的時候就無需宣告版本,也就不會發生多個子模組使用依賴版本不一致的情況。這可以幫助降低衝突的機率。

如果子模組不宣告依賴的使用,即使該例來已經在父POM的dependencyManagement中聲明瞭,也不會產生任何實際的效果。

使用import的依賴範圍的依賴通常指向一個POM,作用是將目標POM中的dependencyManagement配置匯入併合併到當前POM的dependencyManagement元素中。例如想要在另外一個模組中使用dependencyManagement配置,除了複製配置或者繼承這兩種方式之外,還可以使用import範圍依賴將這一配置匯入,如下所示。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.test</groupId>
            <artifactId>a</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

注意,上述程式碼中依賴的type值為pom,import範圍依賴由於其特殊性,一般都是指向打包型別為pom的模組。如果有多個專案,他們使用的依賴版本都是一致的,則就可以定義一個使用denendencyManagement專門管理依賴的POM,然後在各個專案中匯入這些依賴管理配置。

外掛管理

Maven提供了dependencyManagement元素幫助管理依賴,類似的,Maven也提供了pluginManagement元素幫助管理外掛。在該元素中配置的依賴不會造成實際的外掛呼叫行為,當POM中配置了真正的plugin元素,並且其groupId和artifactId與pluginManagement中配置的外掛匹配時,pluginManagement的配置才會影響實際的外掛行為。

例如配置了maven-source-plugin,將其jar-no-fork目標繫結到了verity生命週期階段,以生成專案原始碼包。如果一個專案中有很多子模組,並且需要得到所有這些模組的原始碼包,那麼很顯然,為所有模組重複類似的外掛配置不是最好的辦法。這時更好的方法是在父POM中使用pluginManagement配置外掛,如下所示。

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.1.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

當子模組需要生成原始碼包的時候,只需要如下簡單的配置。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
        </plugin>
    </plugins>
</build>

子模組聲明瞭maven-source-plugin外掛,同時又繼承了父模組的pluginManagement配置。

如果子模組不需要使用父模組中pluginManagement配置的外掛,可以儘管將其忽略。如果子模組需要不同的外掛配置,則可以自行配置以覆蓋父模組的pluginManagement配置。

有了pluginManagement元素,子模組的POM也能得以簡化了。

當專案中的多個模組有同樣的外掛配置時,應當將配置移到父POM的pluginManagement元素中。即使各個模組對於同一外掛的具體配置不盡相同,也應當使用父POM的pluginManagement元素統一宣告外掛的版本。甚至可以要求所有用到的外掛的版本在父POM的pluginManagement元素中宣告,子模組使用外掛時不配置版本資訊,這麼做可以統一專案的外掛版本,避免潛在的外掛不一致或者不穩定問題,也更易於維護。