1. 程式人生 > 其它 >maven系列(五)生命週期和外掛

maven系列(五)生命週期和外掛

除了座標、依賴以及倉庫以外,maven另外兩個核心概念就是生命週期和外掛。maven的生命週期是抽象的,其實際行為都是由外掛來完成。

7.1 什麼是生命週期

在maven出現之前,專案構建的生命週期就已經存在,軟體開發人員每天都在對專案進行清理、編譯、測試以及部署。

Maven的生命週期就是為了對所有的構建過程進行抽象和統一。Maven從大量專案和構建工具中學習和反思,然後總結出一套高度完善的、易擴充套件的生命週期。這個生命週期包含了專案的清理、初始化、編譯、測試、打包、整合測試、驗證、部署和站點生成等幾乎所有構建步驟。也就是說,幾乎所有專案的建立,都能對映到這樣一個生命週期上。

Maven的生命週期是抽象的, 這意味著生命週期本身不做任何實際任務,在maven的設計中,實際的任務都交給外掛來完成。這種思想與設計模式的模板方法(template method)非常相似。

模板方法在父類中定義演算法的整體結構,子類可以通過實現或者重寫父類的方法來控制實際的行為,這樣既保證了演算法有足夠的可擴充套件性,又能夠嚴格控制演算法的整體結構。如下的模板方法抽象類能夠很好的體現maven生命週期的概念。

public abstract class AbstractBuild {
    private AbstractBuild(){}
    public void build() {
        init();
        compile();
        test();
        packagee();
        integrationTest();
        deploy();
    }

    /**
     * 抽象方法 初始化
     */
    protected abstract void init();

    /**
     * 抽象方法 編譯
     */
    protected abstract void compile();

    /**
     * 測試
     */
    protected abstract void test();

    /**
     * 打包:因為package是關鍵詞
     */
    protected abstract void packagee();

    /**
     *  整合測試
     */
    protected abstract void integrationTest();

    /**
     * 部署
     */
    protected abstract void deploy();
}

這個類沒有具體實現初始化,編譯,測試等行為,它們都交由子類去實現。

雖然上述程式碼和Maven實際程式碼相去甚遠,Maven的生命週期包含更多的步驟和更復雜的邏輯,但是它們的基本理念是相同的。為了不讓使用者重複發明輪子,Maven設計了外掛機制。

每個構建步驟都可以繫結一個或多個外掛行為,而且maven為大多數構建步驟編寫並綁定了預設外掛。例如,針對編譯的外掛有maven-compiler-plugin,針對測試的外掛有maven-surefire-plugin等。當用戶有特殊需要的時候,也可以配置外掛定製構建行為,甚至自己編寫外掛。

maven定義的生命週期和外掛機制一方面保證了所以Maven專案有一致的構建標準,另一方面又通過預設外掛簡化和穩定了實際專案的構建。此外,擴充套件性也不錯。

7.2 生命週期詳解

三套生命週期

Maven其實擁有三套相互獨立的生命週期,他們分別為clean、default和site。clean生命週期的目的是清理專案,default生命週期的目的是構建專案,而site則是建立專案站點。

每個生命週期包含一些階段(phase),這些階段是有順序的,且具有依賴的關係。

但是三套生命週期本身是相互獨立的, 使用者可以僅僅呼叫default生命週期的某個階段,而不會對其他生命週期產生任何影響。

clean生命週期

clean生命週期的目的是清理專案,它包含三個階段:

  1. pre-clean:執行一些清理前需要完成的任務
  2. clean:清理上一次構建生成的檔案
  3. post-clean:執行一些清理後需要完成的工作

default生命週期

default生命週期定義了真正構建時所需要的所有步驟,它是所有生命週期中最核心的部分。

  • validate
  • initialize
  • generate-sources:
  • process-sources:處理專案主資原始檔。一般來說,是對src/main/resources目錄的內容進行變數替換等工作後,複製到專案輸出的主classpath目錄中。
  • generate-resources:
  • process-resources:
  • compile:編譯專案的主原始碼。一般來說,是編譯src/main/java目錄下的Java檔案至專案輸出的主classpath目錄中。
  • process-classes
  • generate-test-sources
  • process-test-sources處理專案測試資原始檔。一般來說,是對src/test/resources目錄的內容進行變數替換等工作以後,複製到專案輸出的測試classpath目錄中。
  • generate-test-resources:
  • process-test-resources:
  • test-compile:編譯專案的測試程式碼。一般來說,是編譯src/test/java目錄下的java檔案至專案輸出的測試classpath目錄中。
  • process-test-classes
  • test:使用單元測試框架執行測試,測試程式碼不會被打包或部署。
  • prepare-package
  • package:接受編譯好的程式碼,打包成可釋出的格式,如JAR。
  • pre-integration-test:
  • integration-test
  • post-integration-test:
  • verify:
  • install:將包安裝到maven本地倉庫,供本地其他maven專案使用。
  • deploy:將最終的包複製到遠端倉庫,供其他開發人員和maven專案使用。

site生命週期

site生命週期的目的是建立和釋出專案站點,maven能夠基於pom所包含的資訊,自動生成一個友好的站點,方便團隊交流和釋出專案資訊。該生命週期包括:

  • pre-site:執行一些在生成專案站點之後需要完成的工作。
  • site:生成專案站點文件。
  • post-site:執行一些在生成專案站點之後需要完成的工作。
  • site-deploy:將生成的專案站點發布到伺服器上。

命令列與生命週期

以常見命令解釋其執行的生命週期階段:

  • $mvn clean:該命令呼叫clean生命週期的clean階段。實際執行的階段為clean生命週期的pre-cleanclean階段。
  • $mvn test:該命令呼叫default生命週期的test階段。實際執行的階段為default生命週期的validateinitialize等,直到test的所有階段。這也解釋了為什麼在執行測試的時候,專案的程式碼能夠自動得以編譯。
  • $mvn clean install:該命令呼叫clean生命週期的clean階段和default生命週期的install階段。實際執行的階段為clean生命週期的pre-cleanclean階段,以及default生命週期從validate到install的全部階段。該命令結合了兩個生命週期,在執行正在的專案構建之前清理專案是一個很好的實踐。

7.3 外掛目標

Maven的核心僅僅定義了抽象的生命週期,具體的任務是交給外掛完成的,外掛以獨立的構件形式存在,因此,maven核心的分發包只有3MB,Maven會在需要的時候下載並使用外掛。

maven-dependency-plugin有十多個目標,每個目標對應一個功能,上述提到的幾個功能分別對應dependency:analyzedependency:treedependency:list

冒汗前面是外掛字首,冒號後面是該外掛的目標。

類似的還有:surefire:test這個是maven-surefile-plugin的test目標。

7.4 外掛繫結

Maven的生命週期與外掛相互繫結,用以完成實際的構建任務。具體而言,是生命週期的階段與外掛的目標相互繫結,以完成某個具體的構建任務。

內建繫結

為了能讓使用者幾乎不用任何配置就能構建Maven專案,Maven在核心為一些主要的生命週期階段綁定了很多外掛的目標,當用戶通過命令列呼叫生命週期階段的時候,對應的外掛目標就會執行相應的任務。

clean生命週期僅有的pre-cleancleanpost-clean三個階段,其中的cleanmaven-clean-plugin:clean繫結。

自定義繫結(重點)

除了內建繫結以外,使用者還能夠自己選擇將某個外掛繫結到生命週期的某個階段,這種自定義繫結方法能讓maven專案在構建過程中執行更多工。

一個常見的例子是建立專案的原始碼jar包,內建的外掛繫結關係中並沒有涉及這一任務,因此需要使用者自行配置。maven-source-plugin可以幫助我們完成該任務,它的jar-no-fork目標能夠將專案的主程式碼打包成jar檔案。

<build>
	<plugins>
    	<plugin>
        	<GAV/>
            <executions>
            	<execution>
                	<id>task1</id>
                    <phase>varify</phase>
                    <goals>
                    	<goal/>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

</build>

在POM的build元素下的<plugins>字元素中宣告外掛的使用。對於自定義繫結的外掛,使用者總是應該宣告一個非快照版本,這樣可以避免由於版本變化造成的構建不穩定性。

​ 在上述配置中,除了基本的外掛座標宣告外,還有外掛執行配置,<executions>下每個execution資源時可以用來配置執行一個任務。該例子中配置了一個id為task1的任務,通過phrase配置,將其繫結到verify宣告週期階段上,再通過goals配置指定要執行的外掛目標。至此,自定義外掛繫結完成。執行mvn verify就能看到如下輸出。

我們知道,當外掛目標被繫結到不同的生命週期階段的時候,其執行順序會由生命週期階段的先後順序決定。如果多個目標被繫結到同一個階段,這些外掛宣告的先後順序決定了目標的執行順序。

7.5 外掛配置

完成了外掛和生命週期的繫結之後,使用者還可以配置外掛目標的引數,進一步調整外掛目標所執行的任務,以滿足專案的需求。

命令列外掛配置

使用者可以在maven命令中使用-D引數,並伴隨一個引數鍵=引數值的形式來配置外掛目標的引數。

比如在maven-surefile-plugin中提供了maven.test.skip引數:

mvn install -Dmaven.test.skip=true

-D是用來在啟動一個java程式時設定系統屬性值的。如果該值是一個字串且包含空格,那麼需要包在一對雙引號中。

POM中外掛全域性配置

使用者可以在宣告外掛的時候,對此外掛進行一個全域性的配置。例如我們需要配置maven-compiler-plugin告訴它編譯Java 1.5版本的原始檔,生成與JVM1.5相容的位元組碼:

<build>
	<plugins>
   		<plugin>
        	<groupId>org.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
            	<source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
    </plugins>
</build>

這樣,不管繫結到compile階段的maven-compiler-plugin:compile任務,還是繫結到test-compiler階段的maven-compiler-plugin:testCompiler任務,就都能夠使用該配置,基於1.5版本進行編譯。

POM中外掛任務配置

7.6 獲取外掛資訊

線上外掛資訊

基本上所有主要的maven外掛都來自Apache和codehaus

網址:https://maven.apache.org/plugins/index.html

託管於Codehaus上的Mojo專案也提供了大量maven外掛。但是該網址已經關停了

使用maven-help-plugin描述外掛

mvn help:describe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1

執行了maven-help-plugindescribe目標。在引數plugin中輸入需要描述外掛的GAV。

【略】

常見外掛

外掛名稱 用於 來源
maven-clean-plugin
maven-compiler-plugin
deploy
install
resources 處理資原始檔
maven-site-plugin 生成站點
maven-surefire-plugin 執行測試
maven-jar-plugin 構建jar
maven-javadoc-plugin 生成javadoc文件
maven-pmd-plugin 生成PMD報告
maven-assembly-plugin 構建自定義格式的分發包
maven-enforcer-plugin 定義規則並強制要求專案遵循
maven-source-plugin 生成原始碼包
properties-maven-plugin 從properties檔案讀寫maven屬性
jetty-maven-plugin 整合Jetty容器,實現快速開發測試

7.8 外掛解析機制

為了方便使用者使用和配置外掛,maven不需要使用者提供完整的外掛座標資訊,就可以解析得到正確的外掛,maven的這一特性是一把雙刃劍,雖然它簡化了外掛的使用和配置,可一旦外掛的行為出現異常,使用者就很難快速定位到出現問題的構建。比如

mvn help:system

這一條命令,它到底執行了什麼外掛?下面介紹原理。

外掛倉庫

與依賴構件一樣,外掛構件同樣基於座標儲存在maven倉庫中。在需要的時候,maven會從本地查詢,如果沒有去遠端操作找。

但是maven會區別對待依賴的遠端倉庫和外掛的遠端倉庫。當maven需要的依賴在本地倉庫不存在時,它會去所配置的遠端倉庫查詢,可是當maven需要的外掛在本地倉庫不存在時,它就不會去這些遠端倉庫查詢。

外掛的遠端倉庫使用pluginRepositories和pluginRepository配置。

外掛的預設groupId

在POM中配置外掛的時候,如果該外掛是maven的官方外掛,就可以忽略groupId配置。

<build>
	<plugins>
    	<plugin>
        	<artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
            	<source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
    </plugins>
</build>

上述配置就省略了groupId,maven在解析該外掛的時候,會自動用預設groupId:org.apache.maven.plugins補齊。【不推薦】

解析外掛版本

同樣是為了簡化外掛的配置和使用,在使用者沒有提供版本的情況下,maven會自動解析外掛版本。

首先,maven在超級POM中為所有核心外掛設定了版本,超級POM是所有maven專案的父POM。所有專案都繼承這個超級POM配置。因此,即使使用者不加任何配置,maven使用核心外掛的時候,它們的版本就已經確定了。

如果使用者使用某個外掛時,沒有設定版本,而這個外掛又不屬於核心外掛的範疇,maven就會去檢查所有倉庫中可用的版本,然後做出選擇。

maven遍歷本地倉庫和所有遠端外掛倉庫,將該路徑下的倉庫元資料歸併後,就能計算出latest和release的值。然後使用release而不是latest,避免由於快照頻繁更新導而導致的外掛行為不穩定。

【但是不設定版本是不推薦的, 同樣超級POM中為核心外掛已經設定了版本】

解析外掛字首

外掛字首與groupId:artifactoryId是一一對應的,這種匹配關係儲存在倉庫元資料中。與依賴的groupId/artifactoryId/maven-metadata.xml不同,這裡的倉庫元資料為groupId/maven-metadata.xml

Maven在解析外掛倉庫元資料的時候,會預設使用org.apache.maven.pluginsorg.codehaus.mojo兩個groupId。

也可以通過配置settings.xml讓maven檢查其他groupId上的外掛倉庫元資料:

<settings>
	<pluginGroups>
    	<pluginGroup>com.ssozh.plugins</pluginGroup>
    </pluginGroups>
</settings>

基於該配置,maven就不僅僅會檢查這兩個地方的xml了。還會檢查com/ssozh/plugins/maven-metadata.xml

<metadata>
	<plugins>
    	<plugin>
        	<name>Maven Dependency Plugin</name>
            <prefix>dependency</prefix>
            <artifactId>mavne-dependency-plugin</artifactId>
        </plugin>
    </plugins>
</metadata>

上述內容中可以看出,當Maven解析到dependency:tree這樣的命令的時候,它首先基於預設的groupId歸併所有外掛倉庫的元資料,其次檢查歸併後的元資料,找到對應的artifactId。然後結合當前元資料的groupId。最後使用上述方法獲取version。這時就得到了完整的外掛座標。

如果該maven-metadata.xml沒有記錄該外掛字首,就去mojo然後去自定義,如果都沒有,則報錯。

7.9 小結

本章介紹了maven的生命週期和外掛這兩個重要的概念。不僅解釋了生命週期背後的理念,還詳細闡述了clean、default、site三套生命週期各自的內容。此外,本章還重點介紹了maven外掛如何與生命週期繫結,以及如何配置外掛行為,如何獲取外掛資訊。

補充

generate-source: