1. 程式人生 > >Maven:外掛解析機制。

Maven:外掛解析機制。

為了方便使用者使用和配置外掛,Maven不需要使用者提供完整的外掛座標資訊,就可以解析得到正確的外掛,Maven的這一特性是一把雙刃劍,雖然他簡化了外掛的使用和配置,可一旦外掛的行為出現異常,使用者就很難快速定位到問題的外掛構件。例如mvn help:system這樣一條命令,他到底執行了什麼外掛?該外掛的groupId、artifactId和version分別是什麼?這個構件是從哪裡來的?本文就詳細介紹Maven的執行機制,以讓其不僅知其然,更知其所以然。

外掛倉庫

與依賴構件一樣,外掛構件同樣基於座標儲存在Maven倉庫中。在需要的時候,Maven會從本地倉庫尋找外掛,如果不存在,則從遠端倉庫查詢。找到外掛之後,再下載到本地倉庫使用。

值得一提的是,Maven會區別對待依賴的遠端倉庫與外掛的遠端倉庫。當Maven需要的依賴在本地倉庫不存在時,他會去所配置的遠端倉庫查詢,可是當Maven需要的外掛在本地倉庫不存在時,他就不會去這些遠端倉庫查詢。

不同於repositories及其repository子元素,外掛的遠端倉庫使用pluginRepositories和pluginRepository配置。例如,Maven內建瞭如下的外掛遠端倉庫配置,見下面所示。

<pluginRepositories>
    <pluginRepository>
        <id>central</id>
        <name>Maven Plugin Repository</name>
        <url>http://repo1.maven.org/maven2</url>
        <layout>default</layout>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <updatePolicy>never</updatePolicy>
        </releases>
    </pluginRepository>
</pluginRepositories>

除了pluginRepositories和pluginRepository標籤不同之外,其餘所有子元素表達含義與依賴遠端倉庫配置完全一樣。我們甚至看到,這個預設外掛倉庫的地址就是中央倉庫,他關閉了對SNAPSHOT的支援,以防止引入SNAPSHOT版本的外掛而導致不穩定的構建。

一般來說,中央倉庫所包含的外掛完全能夠滿足我們的需要,因此也不需要配置其他的外掛倉庫。只有在很少的情況下,專案使用的外掛無法在中央倉庫找到,或者自己編寫了外掛,這個時候可以參考上述的配置,在POM或者settings.xml中加入其他的外掛倉庫配置。

外掛的預設groupId

在POM中配置外掛的時候,如果該外掛是Maven的官方外掛(即如果其groupId為org.apache.maven.plugins),就可以省略groupId配置,見下面所示。

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

上述配置中省略了maven-compiler-plugin的groupId,Maven在解析該外掛的時候,會自動用預設groupId org.apache.maven.plugins補齊。

不推薦使用Maven的這一機制,雖然這麼做可以省略一些配置,但這樣的配置會讓團隊中不熟悉Maven的成員感到費解,況且能省略的配置也就僅僅一行而已。

解析外掛版本

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

首先,Maven在超級POM中為所有核心外掛設定了版本,超級POM是所有Maven專案的父POM,所有專案都繼承這個超級POM的配置,因此,即使使用者不加任何配置,Maven使用核心外掛的時候,他們的版本就已經確定了。這些外掛包括maven-clean-plugin、maven-compiler-plugin、maven-surefire-plugin等。

如果使用者使用某個外掛時沒有設定版本,而這個外掛又不屬於核心外掛的範疇,Maven就會去檢查所有倉庫中可用的版本,然後做出選擇。以maven-compiler-plugin為例,他在中央倉庫的倉庫元資料為http://repol.maven.org/maven2/org/apache/maven/plugins/maven-compiler/maven-metadata.xml,其內容如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<metadata>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <versioning>
        <latest>2.1</latest>
        <release>2.1</release>
        <versions>
            <version>2.0-beta-1</version>
            <version>2.0</version>
            <version>2.0.1</version>
            <version>2.0.2</version>
            <version>2.1</version>
        </versions>
    </versioning>
</metadata>

Maven遍歷本地倉庫和所有遠端外掛倉庫,將該路徑下的倉庫元資料歸併後,就能計算出latest和release的值。latest表示所有倉庫中該構件的最新版本,而release表示最新的非快照版本。在Maven 2中,外掛的版本會被解析至latest。也就是說,當用戶使用某個非核心外掛且沒有宣告版本的時候,Maven會將版本解析為所有可用倉庫中的最新版本,而這個版本也可能是快照版。

當外掛的版本為快照版本時,就會出現潛在的問題。Maven會基於更新策略,檢查並使用快照的更新。某個外掛可能昨天還用的好好的,今天就出錯了,其原因就是這個快照版本的外掛發生了變化。為了防止這類問題,Maven 3調整了解析機制,當外掛沒有宣告版本的時候,不再解析至latest,而是使用release。這樣就可以避免由於快照頻繁更新而導致的外掛行為不穩定。

依賴Maven解析外掛版本其實是不推薦的做法,即使Maven 3 將版本解析到最新的非快照版,也還是會有潛在的不穩定性。例如,可能某個外掛釋出了一個新的版本,而這個版本的行為與之前的版本發生了變化,這種變化就可能導致專案構件失敗。因此,使用外掛的時候,應該一直顯式的設定版本,這也解釋了Maven為什麼要在超級POM中為核心外掛設定版本。

解析外掛字首

現在解釋Maven如何根據外掛字首解析得到外掛座標。

外掛字首與groupId:artifactId是一一對應的,這種匹配關係儲存在倉庫元資料中。與groupId/artifactId/maven-metadata.xml不同,這裡的倉庫元資料為groupId/maven-metadata.xml,那麼這裡的groupId是什麼呢?主要的外掛都位於http://repol.maven.org/maven2/org/apache/maven/plugins和http://repository.codehaus.org/org/codehaus/mojo/,相應地,Maven在解析外掛倉庫元資料的時候,會預設使用org.apache.maven.plugins和org.codehaus.mojo兩個groupId。也可以通過配置settings.xml讓Maven檢查其他groupId上的外掛倉庫元資料:

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

基於該配置,Maven就不僅僅會檢查org/apache/maven/plugins/maven-metadata.xml和org/codehaus/mojo/maven-metadata.xml,還會檢查com/your/plugins/maven-metadata.xml。

下面看一下外掛倉庫元資料的內容,見下面。

<metadata>
    <plugins>
        <plugin>
            <name>Maven Clean Plugin</name>
            <prefix>clean</prefix>
            <artifactId>maven-clean-plugin</artifactId>
        </plugin>
        <plugin>
            <name>Maven Compiler Plugin</name>
            <prefix>compiler</prefix>
            <artifactId>maven-compiler-plugin</artifactId>
        </plugin>
        <plugin>
            <name>Maven Dependency Plugin</name>
            <prefix>dependency</prefix>
            <artifactId>maven-dependency-plugin</artifactId>
        </plugin>
    </plugins>
</metadata>

上述內容是從中央倉庫的org.apache.maven.plugins.groupId下外掛倉庫元資料中擷取的一些片段,從這段資料中就能看到maven-clean-plugin的字首為clean,maven-compiler-plugin的字首為compiler,maven-dependency-plugin的字首為dependency。

當Maven解析到dependency:tree這樣的命令後,他首先基於預設的groupId歸併所有外掛倉庫的元資料org/apache/plugins/maven-metadata.xml;其次檢查歸併後的元資料,找到對應的artifactId為maven-dependency-plugin;然後結合當前元資料的groupId為org.apache.maven.plugins;最後解析得到version,這時就得到了完整的外掛座標。如果org/apache/maven/plugins/maven-metadata.xml,以及使用者自定義的外掛組。如果所有元資料中都不包含該字首,則報錯。