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元素中宣告,子模組使用外掛時不配置版本資訊,這麼做可以統一專案的外掛版本,避免潛在的外掛不一致或者不穩定問題,也更易於維護。