讀古今文學網 > Maven實戰 > 18.2 編寫Archetype >

18.2 編寫Archetype

也許你所在組織的一些項目都使用同樣的框架和項目結構,為一個個項目重複同樣的配置及同樣的目錄結構顯然是難以讓人接受的。更好的做法是創建一個屬於自己的Archetype,這個Archetype包含了一些通用的POM配置、目錄結構,甚至是Java類及資源文件,然後在創建項目的時候,就可以直接使用該Archetype,並提供一些基本參數,如groupId、artifactId、version,maven-archetype-plugin會處理其他原本需要手工處理的勞動。這樣不僅節省了時間,也降低了錯誤配置發生的概率。

下面就介紹一個創建Archetype的樣例。首先讀者需要瞭解的是,一個典型的Archetype Maven項目主要包括如下幾個部分:

·pom.xml:Archetype自身的POM。

·src/main/resources/archetype-resources/pom.xml:基於該Archetype生成的項目的POM原型。

·src/main/resources/META-INF/maven/archetype-metadata.xml:Archetype的描述符文件。

·src/main/resources/archetype-resources/**:其他需要包含在Archetype中的內容。

下面結合樣例對上述內容一一詳細解釋。

首先,和任何其他Maven項目一樣,Archetype項目自身也需要有一個POM。這個POM主要包含該Archetype的坐標信息,這樣Maven才能定位並使用它。讀者還要留意,不要混淆Archetype的坐標和使用該Archetype生成的項目的坐標。需要注意的是,雖然Archetype可以說是一種特殊的Maven項目,但maven-archetype-plugin並沒有要求Archetype項目使用特殊的打包類型。因此,一般來說,Archetype的打包類型就是默認值jar。代碼清單18-1展示了一個很簡單的Archetype的POM。

代碼清單18-1 樣例Archetype的POM

接下來要關注的就是Archetype所包含的項目骨架的信息。從本質上來說,在編寫Archetype的時候預先定義好其要包含的目錄結構和文件,同時在必要的地方使用可配置的屬性聲明替代硬編碼。例如,項目的坐標信息一般都是可配置的。代碼清單18-2就是一個簡單的POM原型,它位於Archetype項目資源目錄下的archetype-resources/子目錄中。

代碼清單18-2 樣例Archetype所包含的POM原型

上述代碼片段中的groupId、artifactId和version等信息並沒有直接聲明,而是使用了屬性聲明。回顧18.1.2節,使用Archetype生成項目的時候,用戶一般都需要提供groupId、artifactId、version、package等參數,在那個時候,這些屬性聲明就會由那些參數值填充。

上述POM原型中還包含了一個JUnit依賴聲明和兩個插件配置。事實上,我們可以根據自己的實際需要在這裡提供任何合法的POM配置,在使用該Archetype生成項目的時候,這些配置就是現成的了。

一個Archetype最核心的部分是archetype-metadata.xml描述符文件,它位於Archetype項目資源目錄的META-INF/maven/子目錄下。它主要用來控制兩件事情:一是聲明哪些目錄及文件應該包含在Archetype中;二是這個Archetype使用哪些屬性參數。代碼清單18-3展示了一個Archetype描述符文件。

代碼清單18-3 樣例Archetype描述符文件

該例中的Archetype描述符定義了名稱為sample。它主要包含fileSets和requireProperties兩個部分。其中,fileSets可以包含一個或者多個fileSet子元素,每個fileSet定義一個目錄,以及與該目錄相關的包含或排除規則。

上述代碼片段中的第一個fileSet指向的目錄是src/main/java,該目錄對應於Archetype項目資源目錄的archetype-resources/src/main/java/子目錄。該fileSet有兩個屬性,filtered表示是否對該文件集合應用屬性替換。例如,像${x}這樣的內容是否替換為命令行輸入的x參數的值;packaged表示是否將該目錄下的內容放到生成項目的包路徑下。18.1.2節提到使用Archetype必須提供的參數之一就是package,即項目包名。如果讀者暫時無法理解這兩個屬性的作用,不必著急,稍後通過實例來解釋。

該fileSet還包含了includes子元素,並且聲明了一個值為**/*.java的include規則,表示包含src/main/java/中任意路徑下的java文件。這裡兩個星號**表示匹配任意目錄,一個星號*表示匹配除路徑分隔符外的任意0個或者多個字符。這種匹配聲明的方式在Maven的很多插件中都被用到,如10.5節中的maven-surefire-plugin。除了includes,用戶還可以使用excludes聲明要排除的文件。配置方法與includes類似,這裡不再贅述。

為了能夠說明問題,筆者在src/main/resources/archetype-resources/src/main/java/目錄下創建了一些文件,假設使用該Archetype創建項目的時候,package參數的值為com.juvenxu.mvnbook。表18-1表示了Archetype中文件與生成項目文件的對應關係。

表18-1 Archetype資源文件與所生成項目文件的對應關係

如果fileSet的packaged屬性值為true,directory的值為X,那麼archetype-resources下的X目錄就會對應地在生成的項目中被創建,在生成項目的該X目錄下還會生成一個包目錄,如上例中的com/juvenxu/mvnbook/,最後Archetype中X目錄的子目錄及文件被複製到生成項目X目錄的包目錄下。如果packaged的屬性值為false,那麼Archetype中X目錄下的內容會被直接複製到生成項目的X目錄下。一般來說,Java代碼都需要放到包路徑下,而項目資源文件則不需要。因此,在代碼清單18-3中,第一、第二個對應Java文件的fileSet的packaged的屬性為true,而第三個對應資源文件的fileSet的packaged屬性為false。

還有一點需要解釋的是fileSet的filtered屬性,它表示使用參數值替換屬性聲明,這是個非常有用的特性。例如,表18-1中涉及的幾個Java類都需要有package聲明,而且其值是在項目生成的時候確定的。這時就可以在Java代碼中使用屬性聲明,如App.java的內容應該如代碼清單18-4所示。

代碼清單18-4 Archetype中的App.java

在使用包名com.juvenxu.mvnbook創建項目後,上述代碼中的第一行會變成package com.juvenxu.mvnbook;。

類似地,Dao.java和Service.java的包聲明應該如代碼清單18-5所示。

代碼清單18-5 Archetype中的Dao.java和Service.java

對應地,項目生成後Dao.java的第一行會成為「package com.juvenxu.mvnbook.dao;」,而Service.java的第一行會成為「package com.juvenxu.mvnbook.service;」。使用這樣的技巧,就可以在Archetype中創建多層次的Java代碼。

默認情況下,maven-archetype-plugin要求用戶在使用Archetype生成項目的時候必須提供4個參數:groupId、artifactId、version和package。除此之外,用戶在編寫Archetype的時候可以要求額外的參數。例如,代碼清單18-3就使用了requireProperties配置要求額外的port參數,這樣,Archetype中所有開啟filtered的文件中就可以使用${port}屬性聲明,然後在項目生成的時候用命令行輸入的值填充。

此外,在編寫Archetype的時候還可以為預置的4個參數提供默認值。例如,代碼清單18-3中就為groupId參數提供了默認值com.juvenxu.mvnbook。在組織內部,可能很多項目的groupId是確定的,這時就可以為Archetype提供默認的groupId。

Archetype編寫完成之後,使用mvn clean install將其安裝到本地倉庫。接著用戶就可以通過指定該Archetype的坐標用它生成項目了:

該例使用了交互式的方式生成項目,由於該Archetype為groupId定義了默認值,用戶就不再需要輸入groupId的值了。此外,用戶還不得不輸入該Archetype額外定義的port參數的值。