Maven 聚合與繼承

Maven 聚合與繼承

1. 背景

在這個技術發展飛速的時代,各類用戶對軟件的要求越來越高,軟件本身也變得越

來越複雜。因此,軟件設計人員往往會採用各種方式對軟件劃分模塊,以得到更清晰的

設計及更高的重用性。

Maven 的聚合特性能夠把項目的各個模塊聚合在一起構建,而 Maven 的繼承特性

則能幫助抽取各模塊相同的依賴和插件等配置,在簡化 pom 配置的同時,還能促進各個

模塊配置的一致性。

2. 聚合

到目前爲止,我們都是一個項目一個項目的構建。一個簡單的需求就出來了,我們

會想要一此構建兩個項目,而不是到兩個模塊分別執行 mvn 命令,Maven 聚合(或者

稱爲多模塊)這一特性就是爲該需求服務的。

a) 創建聚合模塊

    需要創建一個新的模塊,來聚合構建項目中其他所有模塊。

<modelVersion>4.0.0</modelVersion>

<groupId>org.lichee</groupId>
<artifactId>lichee</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<name>lichee :: project</name>
<description>This is a project demo of lynch</description>
<inceptionYear>2013-2014</inceptionYear>

<modules>
  <module>parent</module>
  <module>core</module>
  <module>common</module>
  <module>support</module>
  <module>test</module>
  <module>simple-example</module>
</modules>

b) 聚合模塊特徵

    i.   Maven 項目

         聚合模塊本身也是一個 Maven 項目。

    ii.  目錄差異

        準確說除了其他模塊目錄和 pom.xml,沒有其他任何 src、test、target 等相關目錄。

 
 

    iii. pom.xml

        1) 相同的 groupId、version

        2) packaging 必須是 pom

            繼承父模塊也一樣必須是 pom。

        3) name 提供一個相對容易閱讀的名字

            配置合理的 name 字段,會讓 Maven 的構建輸出更清晰。

        4) modules

            實現聚合的最核心配置,用戶可以通過一個打包方式爲 pom 的 Maven 項目中聲明,

            任意數量的 module 元素來實現模塊的聚合。每個 module 的值都是一個當前 pom 的相對目錄。

        5) 模塊所處的目錄名稱與其 artifactId 一致

            文件的目錄名稱和 artifactId 相同,但不絕對,只是 module 裏面的配置,

            必須和文件目錄上的名字一樣。

        6) 推薦目錄格式

            通常將聚合模塊放在項目目錄的最頂層,其他模塊則作爲聚合模塊的子目錄存在,

            方便用戶尋找聚合模塊來構建整個項目。

c) 解析過程

    Maven 會首先解析聚合模塊的 pom、分析要構建的模塊、並計算出一個反應堆構建順序

   (Reactor Build Order),然後根據這個順序依次構建各個模塊。


 

    i.   項目構建小結報告

    ii.  各個模塊構建成功與否、花費時間

    iii. 整個構建花費的時間、使用內存等

3. 繼承

很多項目中,不同的子模塊有着相同的 groupId、version,相同的 spring、junit等依賴,

相同的 maven-compiler-plugin 插件等配置。這就是重複,重複意味着更多的勞動和更多的潛在問題。

在 Maven 世界中,有機制能讓我們抽取出重複的配置,這就是 pom 的繼承。

a) 創建繼承模塊

    我們需要創建 pom 的父子結構,在父 pom 中聲明一些配置供子 pom 繼承,一處聲明,多處使用。

    父模塊:

<groupId>org.lichee</groupId>
<artifactId>lichee-parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>lichee :: parent</name>

    子模塊:

<parent>
<groupId>org.lichee</groupId>
<artifactId>lichee-parent</artifactId>
<version>1.0.0</version>
<relativePath>../parent/</relativePath>
</parent>
<artifactId>lichee-core</artifactId>
<packaging>jar</packaging>
<name>lichee :: core</name>

b) 繼承模塊特徵

    i.   Maven 項目

         繼承模塊本身也是一個 Maven 項目。

    ii.  目錄差異

         準確說除了 pom.xml,沒有其他任何 src、test、target 等相關目錄。

 
 

    iii. pom.xml

        1) groupId、version

            雖然子模塊沒有聲明,不過不代表子模塊沒有這兩個屬性,隱式地從父模塊繼承了這兩個元素,

            這也就消除一些不必要的配置。如果子模塊需要使用和父模塊,

            不一樣的 groupId 或者 version 的情況,那麼用戶完全可以在子模塊中顯式聲明。

            對於 artifactId 元素來說,子模塊應該顯式聲明。

        2) packaging 必須是 pom

            和聚合模塊一樣。

        3) name 提供一個相對容易閱讀的名字

             配置合理的 name 字段,會讓 Maven 的構建輸出更清晰。

        4) relativePath

            表示父模塊 pom 的相對路徑,默認值爲../pom.xml,也就是說,

            Maven默認父 pom 在上一層目錄下。如果子模塊沒有設置正確的 relativePath,

            Maven 將無法找到父 pom,這將直接導致構建失敗。

4. 可繼承的 pom 元素

    a) groupId

        項目組 Id,項目座標的核心元素。

    b) version

        項目版本,項目座標的核心元素。

    c) description

        項目的描述信息。

    d) organization

        項目的組織信息。

    e) inceptionYear

        項目的創始年份。

     f) url

        項目的 url 地址。

    g) developers

        項目的開發者信息。

    h) contributors

        項目的貢獻者信息。

     i) distributionManagement

        項目的部署配置。

     j) issueManagement

        項目的缺陷跟蹤系統信息。

    k) ciManagement

        項目的持續集成系統信息。

     l) scm

        項目的版本控制系統信息。

   m) mailingLists

        項目的郵件信息列表。

    n) properties

        自定義的 Maven 屬性。

    o) dependencies

        項目的依賴配置。

    p) dependencyManagement

        項目的依賴管理配置。

    q) repositories

        項目的倉庫配置。

    r) build

        包括項目的源碼目錄配置、輸出目錄配置、插件配置、插件管理配置等。

    s) reporting

        包括項目的報告輸出目錄配置、報告插件配置等。

5. 依賴管理

通過 dependencies 元素,可以將父模塊的依賴繼承到子類中,但並不是每一個子模塊

都需要相同的依賴。也不合理。

a) dependencyManagement

    讓子模塊繼承到父模塊的依賴配置,又能保證子模塊依賴使用的靈活性。

    在這個元素下的依賴聲明不會引入實際的依賴,不過它能夠約束 dependencies 下的依賴使用。

    父模塊:

<properties>
  <junit.version>4.11</junit.version>
  <spring.version>3.2.5.RELEASE</spring.version>
  <jdk.version>1.6</jdk.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

    子模塊:

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

    i.   變量提取

         spring 和 junit 依賴的版本以 maven 變量的形式提取出來,不僅消除了一些重複,

         也使得個依賴的版本處於更加明顯的位置。

    ii.  引入時機

         dependencyManagement 聲明的依賴不會被任何一個子模塊引入,

         不過這段配置是會被子模塊繼承的。子模塊的引入這些依賴就很簡單了。

         1) 只配置 groupId 和 artifactId

             省去 version 和 scope,這些想信息可以被省略是因爲子模塊繼承了,

             父模塊的 dependencyManagement,完整的依賴聲明在父 pom 中,

             子模塊只需要配置簡單的 groupId 和 artifactId 就能獲得對應的依賴信息,

             從而引入正確的依賴。

         2) 選擇性引入依賴

             如果子模塊的 pom 中不聲明某個依賴的使用,即使該依賴已經在父 pom

             的 dependencyManagement 中聲明瞭,也不會產生任何實際的效果,

             也就是說不會被引入到子模塊的依賴中。

        3) 最佳推薦

            這種依賴管理機制似乎不能減少太多的 pom 配置,不過還是推薦採用這種方式。

            因爲在父 pom 中使用 dependencyManagement 聲明依賴能夠統一項目範圍中的依賴版本,

            子模塊在使用過程中無需聲明版本,也不會發生多個子模塊使用的依賴版本不一致,

            這可以降低依賴衝突的機率,對於後期升級或者修改依賴版本,也是提供了大大的便利。

        4) import 依賴範圍

            這個範圍的依賴只在 dependencyManagement 元素下才有效果,使用該範圍的依賴通常

            指向一個 pom,作用是將目標 pom 中的dependencyManagement 配置導入併合併到,

            當前 pom 的dependencyManagement 元素中。import 範圍依賴由於其特俗性,

            一般都是指向打包類型爲 pom 的模塊。

6. 插件管理

Maven 提供了 dependencyManagement 元素幫助管理依賴,類似的,

Maven 也提供了 pluginManagement 元素幫助管理插件。

a) pluginManagement

    該元素中配置的依賴插件不會造成實際插件的行爲,當子模塊 pom 中配置了真正的plugin 元素,

    並且 groupId、artifactId 與 pluginManagement 中配置的插件匹配時,

    pluginManagement 的配置纔會影響實際的插件行爲。

    父模塊:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <showWarnings>true</showWarnings>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

    子模塊:

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

    i.   變量提取

         和 dependencyManagement 一樣。

    ii.  引入時機

         和 dependencyManagement 一樣。

    iii. 最佳推薦

        當項目中的多個模塊有同樣的插件配置時,應當將配置移到父 pom 的pluginManagement 元素中。

        甚至可以要求將所有用到的插件的版本在父pom的pluginManagement元素中聲明,

        子模塊使用插件時不配置版本信息,這麼做可以統一插件的版本,

        避免潛在的插件不一致或者不穩定問題,也便易於維護。

7. 聚合與繼承的關係

a) 相同點

     聚合 pom 和繼承關係中的父 pom 的 packaging 都必須是 pom,

     聚合模塊與繼承關係中的父模塊除了 pom 之外都沒有實際的內容。

     往往也會發現,一個 pom 既是聚合 pom,也是父 pom,這麼做主要是爲了方便,

     融合使用聚合與繼承也沒有什麼問題。

b) 不同點

    i.   聚合

         爲了方便快速構建項目。它知道有哪些被聚合的模塊,但那些被聚合的模塊不知道這個聚合模塊。

    ii.  繼承

         爲了消除重複配置。它不知道有哪些子模塊繼承與它,但那些子模塊都必須知道,

         自己的父 pom 是什麼。

c) 如圖所示


 

8. 約定優於配置

Maven 提倡“約定優於配置”(Convention Over Configuration),這是 Maven最核心的設計理念之一。

原因之一就是使用約定可以大量減少配置。

a) 源碼目錄爲 src/main/java/

b) 源碼資源目錄爲 src/main/resources/

c) 測試目錄爲 src/test/java/

d) 測試資源目錄爲 src/test/resources/

e) 編譯輸出目錄爲 target/classes/

f) 打包方式爲 jar

g) 包輸出目錄爲 target/

h) 超級 pom

    超級 pom 定義以上的目錄結構、核心插件設定版本。Maven 設定核心插件的原因,

    是防止由於插件版本的變化而造成構建的不穩定。

    遵循約定雖然損失了一定的靈活性,用戶不能隨意安排目錄結構,但是卻能減少配置。

    更重要的是,遵循約定能夠幫用戶遵循構建標準。個性往往意味着犧牲通用性,

    意味着增加無謂的複雜度。

9. 反應堆

反應堆(Reactor)是指所有模塊組成的一個構建結構。對於單模塊的項目,反應堆就

是該模版本身,但對於多模塊項目來說,反應堆就包含了各模塊之間繼承與依賴的關係。

a) 反應堆的構建順序

    一般情況按照 modules 的聲明順序,不過也有特俗情況。Maven 按序讀取 pom,

    如果該 pom 沒有依賴模塊,那麼就構建該模塊,否則就先構建其依賴模塊,

    如果該依賴還依賴於其他模塊,則進一步先構建依賴的依賴。

    i.   繼承或者依賴

         Maven 還需要考慮模塊之間的繼承和依賴關係。

    ii.  有向非循環圖

         模塊間的依賴關係會將反應堆構成一個有向非循環圖。

b) 裁剪反應堆

    用戶會想要僅僅構建完整反應堆中的某些個模塊。也就是用戶需要實時地裁剪反應堆。

    Maven 提供很多命令行選項支持裁剪反應堆,mvn –h 查看。

發佈了70 篇原創文章 · 獲贊 10 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章