maven系列(五)生命週期和外掛
除了座標、依賴以及倉庫以外,maven另外兩個核心概念就是生命週期和外掛。maven的生命週期是抽象的,其實際行為都是由外掛來完成。
7.1 什麼是生命週期
在maven出現之前,專案構建的生命週期就已經存在,軟體開發人員每天都在對專案進行清理、編譯、測試以及部署。
Maven的生命週期就是為了對所有的構建過程進行抽象和統一。Maven從大量專案和構建工具中學習和反思,然後總結出一套高度完善的、易擴充套件的生命週期。這個生命週期包含了專案的清理、初始化、編譯、測試、打包、整合測試、驗證、部署和站點生成等幾乎所有構建步驟。也就是說,幾乎所有專案的建立,都能對映到這樣一個生命週期上。
Maven的生命週期是抽象的, 這意味著生命週期本身不做任何實際任務,在maven的設計中,實際的任務都交給外掛來完成。這種思想與設計模式的模板方法(template method)非常相似。
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生命週期的目的是清理專案,它包含三個階段:
- pre-clean:執行一些清理前需要完成的任務
- clean:清理上一次構建生成的檔案
- 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-clean
和clean
階段。$mvn test
:該命令呼叫default生命週期的test階段。實際執行的階段為default生命週期的validate
、initialize
等,直到test
的所有階段。這也解釋了為什麼在執行測試的時候,專案的程式碼能夠自動得以編譯。$mvn clean install
:該命令呼叫clean生命週期的clean階段和default生命週期的install
階段。實際執行的階段為clean
生命週期的pre-clean
和clean
階段,以及default生命週期從validate到install的全部階段。該命令結合了兩個生命週期,在執行正在的專案構建之前清理專案是一個很好的實踐。
7.3 外掛目標
Maven的核心僅僅定義了抽象的生命週期,具體的任務是交給外掛完成的,外掛以獨立的構件形式存在,因此,maven核心的分發包只有3MB,Maven會在需要的時候下載並使用外掛。
maven-dependency-plugin有十多個目標,每個目標對應一個功能,上述提到的幾個功能分別對應dependency:analyze
,dependency:tree
和dependency:list
。
冒汗前面是外掛字首,冒號後面是該外掛的目標。
類似的還有:surefire:test
這個是maven-surefile-plugin
的test目標。
7.4 外掛繫結
Maven的生命週期與外掛相互繫結,用以完成實際的構建任務。具體而言,是生命週期的階段與外掛的目標相互繫結,以完成某個具體的構建任務。
內建繫結
為了能讓使用者幾乎不用任何配置就能構建Maven專案,Maven在核心為一些主要的生命週期階段綁定了很多外掛的目標,當用戶通過命令列呼叫生命週期階段的時候,對應的外掛目標就會執行相應的任務。
clean生命週期僅有的pre-clean
、clean
和post-clean
三個階段,其中的clean
與maven-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-plugin
的describe
目標。在引數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.plugins
和org.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: