Apache Maven 之所以強大,是因為他有一個強大的 Plugin 執行框架,你任何想讓 Maven 幫你完成的工作,無論是建置(Build)、封裝(Packaging)、產生報表(Reporting)、執行測試(Tests),全部都是透過 Plugins 完成的。它除了內建的核心 Plugins 之外,還有數以百計的第三方 Plugins 可以安裝使用。今天這篇文章我就來介紹一下他的基本架構與使用方式。
簡介 Apache Maven Plugins
基本上 Apache Maven 的 Plugins 分成兩大類,並且可以定義在專案的 pom.xml 檔案中:
-
建置外掛 (Build plugins)
用來執行在建置(build)過程中的所有大小事,他會定義在 POM 檔的 <build/> 元素底下。
-
報表外掛 (Reporting plugins)
用來執行在建立文件網站(site)過程中的所有大小事,他會定義在 POM 檔的 <reporting/> 元素底下。
由於報表外掛(Reporting plugins)的結果會被發佈在文件網站(site)上,所以必須考量多國語系(internationalized)與本地化(localized)的要求。詳見 Localization of Plugins 說明。
Apache Maven 官方支援許多 Plugins,並且詳細列在 Available Plugins 頁面中,光是這些內建的 Plugins 就可以幫我們解決 95% 以上的建置或報表工作,真的相當厲害且富有內涵,我很難在一篇文章內介紹這麼多 Plugins,只能淺淺帶過,有興趣大家可以個別研究不同的 Plugins。
突然覺得可以所有 Plugins 都介紹的話,那就可以參加 2022 iThome 鐵人賽 了吧!😅
以下我就列出這些官方支援的 Plugins 清單,大家可以看的大概,有點印象即可:
除了 Maven 官方支持的 Plugins 外,還有個 MojoHaus Maven Plugins Project 由社群維護了一系列好用的 Plugins,有空的時候去翻看看,搞不好有意外之喜。在這個 MojoHaus 的世界裡所稱呼的 Mojo (Maven plain Old Java Object) 通常是指 Plugin 中的某個 Goal 實作,很多 Plugin 的 Goal 在實作的時候也會以 ___Mojo 結尾,來當成 Goal 的方法名稱,是一種 Maven plugins 常見的命名規則。我們其實經常會在許多文件中看到這個單字,而這個 Mojo 單字有「魔力」、「魅力」等含意!
認識 Maven 的建置生命週期(lifecycle)、階段(phase)與目標(goal)
在 Maven 裡面有許多「抽象概念」經常會混淆著初學者,因為你只要沒有認真看懂這些概念,根本就無法好好的使用 Maven 這套優異的建置工具。
首先,你要先知道 Maven 定義了三種不同的生命週期,彼此相關而不重複:
-
default
處理專案的建置(build)與部署(deployment)等工作
-
clean
處理專案的清理工作,將建置過程中產生的檔案清除
-
site
處理專案文件網站的產生工作
你從官網的 Lifecycles Reference 文件可以發現,每個不同的生命週期(lifecycle)分別定義了一系列依序執行的階段(phase),這些階段是 Maven 依據過往數十年的最佳實務整理出來的寶貴經驗。
請注意,這些所謂的「生命週期」與「階段」都只是個「概念」而已,所代表意義是:
-
當你想要建置與部署時 (default),你必須依據 Maven 所定義的階段來執行任務
<phases>
<phase>validate</phase>
<phase>initialize</phase>
<phase>generate-sources</phase>
<phase>process-sources</phase>
<phase>generate-resources</phase>
<phase>process-resources</phase>
<phase>compile</phase>
<phase>process-classes</phase>
<phase>generate-test-sources</phase>
<phase>process-test-sources</phase>
<phase>generate-test-resources</phase>
<phase>process-test-resources</phase>
<phase>test-compile</phase>
<phase>process-test-classes</phase>
<phase>test</phase>
<phase>prepare-package</phase>
<phase>package</phase>
<phase>pre-integration-test</phase>
<phase>integration-test</phase>
<phase>post-integration-test</phase>
<phase>verify</phase>
<phase>install</phase>
<phase>deploy</phase>
</phases>
-
當你想要清理時 (clean),你必須依據 Maven 所定義的階段來執行任務
<phases>
<phase>pre-clean</phase>
<phase>clean</phase>
<phase>post-clean</phase>
</phases>
<default-phases>
<clean>
org.apache.maven.plugins:maven-clean-plugin:2.5:clean
</clean>
</default-phases>
-
當你想要建立文件網站時 (site),你必須依據 Maven 所定義的階段來執行任務
<phases>
<phase>pre-site</phase>
<phase>site</phase>
<phase>post-site</phase>
<phase>site-deploy</phase>
</phases>
<default-phases>
<site>
org.apache.maven.plugins:maven-site-plugin:3.3:site
</site>
<site-deploy>
org.apache.maven.plugins:maven-site-plugin:3.3:deploy
</site-deploy>
</default-phases>
請注意: 所有的階段(phase)其名稱都是不重複的。
瞭解了這些生命週期與階段的概念之後,你就必須知道,所有的 Java 專案都可以依據實際的需求來定義特定階段(phase)要執行什麼樣的目標(goal)!
所以,生命週期(lifecycle)與階段(phase)就只是個概念(Concepts),而目標(goal)才是你真正要做的事情 (要達成的目標)!
我用一個非常簡單的例子來說明:
-
我們用以下命令建立一個全新的 Java 專案
mvn archetype:generate -B `
'-DarchetypeCatalog=internal' `
'-DgroupId=com.duotify' `
'-DartifactId=demo1' `
'-Dversion=1.0-SNAPSHOT'
由於 Maven 預設會採用 org.apache.maven.archetypes:maven-archetype-quickstart:1.0 這個專案範本(archetype),所以不用寫底下這段較長的命令:
mvn archetype:generate -B `
'-DarchetypeCatalog=internal' `
'-DgroupId=com.duotify' `
'-DartifactId=demo1' `
'-Dversion=1.0-SNAPSHOT' `
'-DarchetypeGroupId=org.apache.maven.archetypes' `
'-DarchetypeArtifactId=maven-archetype-quickstart' `
'-DarchetypeVersion=1.0'
這只是一個非常簡單的 Console 專案:

其 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.duotify</groupId>
<artifactId>demo1</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>demo1</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
-
然後我們直接執行 mvn 命令,並獲取以下錯誤訊息
[INFO] Total time: 0.133 s
[INFO] Finished at: 2022-09-11T18:04:06+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/NoGoalSpecifiedException
這裡的重點在這段:
No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy.
這段英文應該不難理解,你必須在 mvn 命令後面指定一個 phase 或 goal 才能執行 Maven!
-
假設我們只要編譯目前的專案,那我們可以指定 compile 這個階段
mvn compile
上述命令執行時會掛掉,容後補充說明。
由於這個 compile 階段隸屬於 default 生命週期,因此 Maven 就會從 default 生命週期的第一個 phase 開始找尋是否有相對應的目標(goal)需要達成。
所以 Maven 會依序去查找 validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile 這幾個階段是否有相對應的目標(goal)去執行程式。
基本上,整個 Maven 就是這樣跑起來的!👍
此時重點就要登場了,所以我們所說的目標(goal)具體來說到底是什麼呢?
理解 Plugin 與 Goal 之間的關係
事實上,我們註冊到 pom.xml 的每一個 Plugins,裡面分別都「實作」了一個到多個目標(goals),而這些 Plugin 裡面的目標(goals)會明確綁定到特定階段(phase),當 Maven 執行到該階段(phase)的時候,就會自動執行這個 Plugin 的目標(goal)。說穿了,所謂的目標(goal)就是一個類別中的方法而已。
一般來說,在 Plugin 裡面的目標名稱(goal name)經常會跟階段名稱(phase name)刻意設計的一樣,這樣也比較好辨識,但這並不是必要條件。
我以 Apache Maven Compiler Plugin 為例,這個 Plugin 實作了 3 個目標:
-
compiler:compile
用來綁定 compile phase 並用來編譯 Java 主程式的原始碼(src/main/**)。
-
compiler:testCompile
用來綁定 test-compile phase 並用來編譯 Java 測試程式的原始碼(src/test/**)。
-
compiler:help
用來顯示這個 compiler plugin 的使用說明。
幾乎所有 Plugin 都會實作 help 這個目標(goal),方便開發人員查詢相關用法。你可以執行 mvn compiler:help 查詢相關用法。
有趣的地方在於,在官網的 Plugin Documentation 文件中,並沒有明確跟你說這些 Goal 要怎樣使用、用在哪裡,這些細節已經被定義在 Maven 的生命週期(lifecycle)、階段(phase)與目標(goal)之中了。因此你若沒有先建立好完整且清晰的概念,就會無法理解 Maven 的運作原理。
我們接續上一段的例子,當我們執行 mvn compile 命令的時候,實際上完整的流程如下:
-
先選中 compile 階段(phase),藉此判斷出 default 這個生命週期(lifecycle)
-
依序執行 default 生命週期(lifecycle)的每個階段(phase)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
validate 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
initialize 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
generate-sources 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
process-sources 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
generate-resources 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
process-resources 階段(phase)的目標(goal)
- 找尋所有 Plugins 中是否有實作並綁定任何名稱為
compile 階段(phase)的目標(goal)
如此一來 mvn compile 命令便執行完畢!
由於 compile plugin 是 Maven 的核心 plugins 之一,所以預設就會註冊在 pom.xml 之中,不用特別設定,而上述過程只有 compile 這個目標(goal)有被比對中,因此到 compile 這個階段(phase)的時候,就會跑去執行 compile:compile 目標(goal)。
如果我直接執行 mvn compile:compile 的話,就代表我們執行的是 compile 這個 plugin 的 compile 目標而已,不會去執行其他階段(phase),因此也不會去執行其他 Plugins 中的任何目標。
所以 mvn compile 與 mvn compile:compile 所代表的意義是截然不同的!
解決無法編譯的問題
我先前有提到,這個範例專案其實無法編譯,你在執行 mvn compile 的時候會發現以下訊息:
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------< com.duotify:demo1 >--------------------------
[INFO] Building demo1 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ demo1 ---
[WARNING] Using platform encoding (MS950 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory G:\Projects\demo1\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ demo1 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding MS950, i.e. build is platform dependent!
[INFO] Compiling 1 source file to G:\Projects\demo1\target\classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
[INFO] 2 errors
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.991 s
G:\Projects\demo1>mvn compile:compile
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml (14 kB at 14 kB/s)
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml (21 kB at 21 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.733 s
[INFO] Finished at: 2022-09-11T18:40:45+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] No plugin found for prefix 'compile' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (C:\Users\wakau\.m2\repository), central (https://repo.maven.apache.org/maven2)] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/NoPluginFoundForPrefixException
你可以從上述訊息得知,其實這個 mvn compile 在執行的過程中,執行了 2 個目標:
maven-resources-plugin:2.6:resources
maven-compiler-plugin:3.1:compile
而在執行 maven-compiler-plugin:3.1:compile 目標時,卻發生了以下錯誤:
[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
首先,看起來 Maven 內建的 maven-compiler-plugin 選用了 3.1 版本有點過於老舊,他預設使用 Java 5 來編譯原始碼,但我的電腦裝的是 JDK 17,編譯器最低支援版本從 Java 7 開始支援,所以才會編譯失敗。
要解決這個問題其實很簡單,只要在專案的 pom.xml 加入這個 maven-compiler-plugin plugin 並提供選項設定即可。
我打算加入以下 XML 片段到 pom.xml 檔案中,藉此指定最新版的 maven-compiler-plugin plugin:
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
</plugins>
</build>
...
</project>
再重新執行一次 mvn compile 就會發現專案已經可以成功建置!
其實為了讓建置過程可以更穩定,一般來說我們會明確指定各個 Plugins 的版本資訊(<version>3.10.1</version>),否則當 Plugin 版本變化時,若是遇到破壞性更新(Breaking changes)那就遭了!

除了指定 maven-compiler-plugin plugin 到最新版本外,你也可以透過 plugin 的 <configuration> 來指定參數,讓 maven-compiler-plugin 可以選用 1.7 當成我們的原始碼版本,這樣也能讓專案順利編譯成功!
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
再重新執行一次 mvn compile 或 mvn clean compile 也會發現專案依然可以成功建置!
注意: 執行 mvn clean compile 命令意味著先執行 clean 階段,再執行 compile 階段!

除了上述兩種方法外,還有一種直接透過 CLI 傳入 -D 指定 maven.compiler.source 與 maven.compiler.target 屬性的用法,也可以在執行時動態改變編譯器版本設定,詳見 compiler:compile 的 Optional Parameters 說明:
mvn clean compile '-Dmaven.compiler.source=8' '-Dmaven.compiler.target=8'
這個方法也可以通過編譯!
將應用程式封裝成 JAR 檔
最後,我想在多介紹 Apache Maven JAR Plugin,如果我們想要把應用程式打包成 JAR 檔,就需要用到這個內建的 plugin,以本文的範例來說,還需要對這個 plugin 做出一些調整才行。
你至少需要加入以下設定到 pom.xml 的 <project><build><plugins> 底下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.duotify.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
注意: 其實 <groupId>org.apache.maven.plugins</groupId> 是可以省略的,因為 org.apache.maven.plugins 這個 groupId 是 Maven 的預設值。
然後執行以下 mvn clean package 命令即可產生 target\demo1-1.0-SNAPSHOT.jar 檔:
mvn clean package '-Dmaven.compiler.source=8' '-Dmaven.compiler.target=8'
接著就可以用以下命令直接測試執行:
java -jar target/demo1-1.0-SNAPSHOT.jar
總結
有了上述這個完整的例子,我相信你應該已經學會基本的 Maven Plugins 的運作架構與基本設定方法!👍
另外,你可以試試執行以下命令,他可以幫你找出你目前專案最終生效的 POM 檔完整內容,包含那些 Maven 內建的 POM 設定值,都會出現在執行結果中:
mvn help:effective-pom
上述命令事實上是執行 help plugin 的 effective-pom 目標(goal)!
你還可以利用以下命令,找出特定一個階段(phase)是哪些 plugins 有綁定目標(goal)
mvn help:describe -Dcmd=compile
他會顯示相當清楚的結果:
[INFO] 'compile' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
It is a part of the lifecycle for the POM packaging 'jar'. This lifecycle includes the following phases:
* validate: Not defined
* initialize: Not defined
* generate-sources: Not defined
* process-sources: Not defined
* generate-resources: Not defined
* process-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:resources
* compile: org.apache.maven.plugins:maven-compiler-plugin:3.1:compile
* process-classes: Not defined
* generate-test-sources: Not defined
* process-test-sources: Not defined
* generate-test-resources: Not defined
* process-test-resources: org.apache.maven.plugins:maven-resources-plugin:2.6:testResources
* test-compile: org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile
* process-test-classes: Not defined
* test: org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test
* prepare-package: Not defined
* package: org.apache.maven.plugins:maven-jar-plugin:2.4:jar
* pre-integration-test: Not defined
* integration-test: Not defined
* post-integration-test: Not defined
* verify: Not defined
* install: org.apache.maven.plugins:maven-install-plugin:2.4:install
* deploy: org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy
相關連結