徹底理解maven

前言
最近解決jar包沖突問(wèn)題時(shí),很頭疼,發(fā)現(xiàn)自己對(duì)maven的理解太膚淺了,很多細(xì)節(jié)都一知半解,于是最近又學(xué)習(xí)了一把maven,總結(jié)如下:
基本概念
maven有兩個(gè)最基本的概念: pom和lifecycle, 這里的pom不是maven構(gòu)建過(guò)程中使用的pom文件,但他們之間有聯(lián)系。 pom全稱為Project Object Model, 簡(jiǎn)單說(shuō)就是要對(duì)構(gòu)建的項(xiàng)目進(jìn)行建模,將要構(gòu)建的項(xiàng)目看成是一個(gè)對(duì)象Object,既然是一個(gè)對(duì)象,這個(gè)對(duì)象有哪些屬性呢? 在maven中一個(gè)項(xiàng)目使用唯一的坐標(biāo)來(lái)表示,它包括groupId, artifactId, version, classifier, type(也叫packaging)這五部分,另外一個(gè)方面,一個(gè)項(xiàng)目肯定不是孤立存在的,可能會(huì)依賴其他項(xiàng)目,也就是說(shuō)這個(gè)對(duì)象應(yīng)該還有dependencies屬性,用PO表示構(gòu)建對(duì)象,使用java代碼描述這個(gè)對(duì)象的話:

創(chuàng)新新互聯(lián),憑借十年的成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn),本著真心·誠(chéng)心服務(wù)的企業(yè)理念服務(wù)于成都中小企業(yè)設(shè)計(jì)網(wǎng)站有成百上千案例。做網(wǎng)站建設(shè),選成都創(chuàng)新互聯(lián)公司。

class PO{
    private String groupId;
    private String artifactId;
    private String version;
    private String classifier;
    private String type;
    private Set<PO> dependencies;
}

xml具有很強(qiáng)的表達(dá)能力,一個(gè)java對(duì)象可以用xml來(lái)描述,用xml表達(dá)上面這個(gè)java對(duì)象可以為:

<PO>
    <groupId></groupId>
    <artifactId></artifactId>
    <version></version>
    <classifier><classifier>
    <type></type>
    <dependencies>
        <PO></PO>
        <PO></PO>
        ...
    </dependencies>
</PO>

這個(gè)是不是和pom.xml很類似? 其實(shí)pom.xml就是PO對(duì)象的xml描述,上面這個(gè)PO定義還不完整,我們知道在java中類是可以繼承的,PO也有繼承關(guān)系,PO對(duì)象存在父類父類對(duì)象,用parent表示,它會(huì)繼承父類對(duì)象的所有屬性。 另一方面,一個(gè)項(xiàng)目可能根據(jù)不同職責(zé)分為多個(gè)模塊(module),所有模塊其實(shí)也就是一個(gè)單獨(dú)的項(xiàng)目,只不過(guò)這些項(xiàng)目會(huì)使用其父對(duì)象的一些屬性來(lái)進(jìn)行構(gòu)建。我們將這些新的屬性加到PO的定義中去:

class PO{
    private String groupId;
    private String artifactId;
    private String version;
    private String classifier;
    private String type;
    private Set<PO> dependencies;
    private PO parent;
    private Set<PO> modules;
}

再將這個(gè)定義用XML語(yǔ)言表示一下:

<PO>
    <parent></parent>
    <groupId></groupId>
    <artifactId></artifactId>
    <version></version>
    <classifier><classifier>
    <type></type>
    <dependencies>
        <PO></PO>
        <PO></PO>
        ...
    </dependencies>
    <modules>
        ...
    </modules>
</PO>

是不是更像pom.xml了? pom.xml其實(shí)就是對(duì)PO對(duì)象的xml描述??!
構(gòu)建
項(xiàng)目的構(gòu)建過(guò)程對(duì)應(yīng)的是PO對(duì)象的build屬性,對(duì)應(yīng)pom.xml中也就是<build>元素中的內(nèi)容,這里就有引入maven中第二個(gè)核心概念:Lifecycle。Lifecycle直譯過(guò)來(lái)就是生命周期。我們平常會(huì)接觸到哪些周期呢?一年中春夏秋冬就是一個(gè)周期。一個(gè)周期中可能分為多個(gè)階段,比如這里的春夏秋冬。在maven中一個(gè)構(gòu)建過(guò)程就對(duì)應(yīng)一個(gè)Lifecycle,這個(gè)Lifecycle也分為多個(gè)階段,每個(gè)階段叫做phase。你可能會(huì)問(wèn),那這個(gè)Lifecycle中包含多少個(gè)phase呢?一個(gè)標(biāo)準(zhǔn)的構(gòu)建Lifecycle包含了如下的phase:

validate: 用于驗(yàn)證項(xiàng)目的有效性和其項(xiàng)目所需要的內(nèi)容是否具備
initialize:初始化操作,比如創(chuàng)建一些構(gòu)建所需要的目錄等。
generate-sources:用于生成一些源代碼,這些源代碼在compile phase中需要使用到
process-sources:對(duì)源代碼進(jìn)行一些操作,例如過(guò)濾一些源代碼
generate-resources:生成資源文件(這些文件將被包含在最后的輸入文件中)
process-resources:對(duì)資源文件進(jìn)行處理
compile:對(duì)源代碼進(jìn)行編譯
process-classes:對(duì)編譯生成的文件進(jìn)行處理
generate-test-sources:生成測(cè)試用的源代碼
process-test-sources:對(duì)生成的測(cè)試源代碼進(jìn)行處理
generate-test-resources:生成測(cè)試用的資源文件
process-test-resources:對(duì)測(cè)試用的資源文件進(jìn)行處理
test-compile:對(duì)測(cè)試用的源代碼進(jìn)行編譯
process-test-classes:對(duì)測(cè)試源代碼編譯后的文件進(jìn)行處理
test:進(jìn)行單元測(cè)試
prepare-package:打包前置操作
package:打包
pre-integration-test:集成測(cè)試前置操作   
integration-test:集成測(cè)試
post-integration-test:集成測(cè)試后置操作
install:將打包產(chǎn)物安裝到本地maven倉(cāng)庫(kù)
deploy:將打包產(chǎn)物安裝到遠(yuǎn)程倉(cāng)庫(kù)

在maven中,你執(zhí)行任何一個(gè)phase時(shí),maven會(huì)將其之前的phase都執(zhí)行。例如 mvn install,那么maven會(huì)將deploy之外的所有phase按照他們出現(xiàn)的順序一次執(zhí)行。
Lifecycle還牽涉到另外一個(gè)非常重要的概念:goal。注意上面Lifecycle的定義,也就是說(shuō)maven為程序的構(gòu)建定義了一套規(guī)范流程:第一步需要validate,第二步需要initialize... ... compile,test,package,... ... install,deploy,但是并沒(méi)有定義每一個(gè)phase具體應(yīng)該如何操作。這里的phase的作用有點(diǎn)類似于Java語(yǔ)言中的接口,只協(xié)商了一個(gè)契約,但并沒(méi)有定義具體的動(dòng)作。比如說(shuō)compile這個(gè)phase定義了在構(gòu)建流程中需要經(jīng)過(guò)編譯這個(gè)階段,但沒(méi)有定義應(yīng)該怎么編譯(編譯的輸入是什么?用什么編譯javac/gcc?)。這里具體的動(dòng)作就是由goal來(lái)定義,一個(gè)goal在maven中就是一個(gè)Mojo(Maven old java object)。Mojo抽象類中定義了一個(gè)execute()方法,一個(gè)goal的具體動(dòng)作就是在execute()方法中實(shí)現(xiàn)。實(shí)現(xiàn)的Mojo類應(yīng)該放在哪里呢?答案是maven plugin里,所謂的plugin其實(shí)也就是一個(gè)maven項(xiàng)目,只不過(guò)這個(gè)項(xiàng)目會(huì)引用maven的一些API,plugin項(xiàng)目也具備maven坐標(biāo)。
在執(zhí)行具體的構(gòu)建時(shí),我們需要為lifecycle的每個(gè)phase都綁定一個(gè)goal,這樣才能夠在每個(gè)步驟執(zhí)行一些具體的動(dòng)作。比如在lifecycle中有個(gè)compile phase規(guī)定了構(gòu)建的流程需要經(jīng)過(guò)編譯這個(gè)步驟,而maven-compile-plugin這個(gè)plugin有個(gè)compile goal就是用javac來(lái)將源文件編譯為class文件的,我們需要做的就是將compile這個(gè)phase和maven-compile-plugin中的compile這個(gè)goal進(jìn)行綁定,這樣就可以實(shí)現(xiàn)Java源代碼的編譯了。那么有人就會(huì)問(wèn),在哪里綁定呢?答案是在pom.xml<build>元素中配置即可。例如:

<build>
<plugins>
  <plugin>
    <artifactId>maven-myquery-plugin</artifactId>
    <version>1.0</version>
    <executions>
      <execution>
        <id>execution1</id>
        <phase>test</phase>
        <configuration>
          <url>http://www.foo.com/query</url>
          <timeout>10</timeout>
          <options>
            <option>one</option>
            <option>two</option>
            <option>three</option>
          </options>
        </configuration>
        <goals>
          <goal>query</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>
</build>

就將maven-myquery-plugin中的query這個(gè)goal綁定到了test這個(gè)phase,后續(xù)在maven執(zhí)行到test phase時(shí)就會(huì)執(zhí)行query goal。還有有人可能會(huì)問(wèn),我都沒(méi)有指定Java源文件的位置,編譯啥?這就引出了maven的design principle。在maven中,有一個(gè)非常著名的principle就是convention over configuration(約定優(yōu)于配置)。這一點(diǎn)和ant有非常大的區(qū)別,例如使用ant來(lái)進(jìn)行編譯時(shí),我們需要指定源文件的位置,輸出文件的位置,javac的位置,classpath... ...在maven中這些都是不需要,若沒(méi)有手動(dòng)配置,maven默認(rèn)從<項(xiàng)目根目錄>/src/main/java這個(gè)目錄去查找Java源文件,編譯后的class文件會(huì)保存在<項(xiàng)目根目錄>/target/classes目錄。在maven中,所有的PO都有一個(gè)根對(duì)象,就是Super POM。Super POM中定義了所有的默認(rèn)的配置項(xiàng),Super POM對(duì)應(yīng)的pom.xml文件可以在maven安裝目錄下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到。用一張圖來(lái)表示maven Lifecycle,phase,goal之間的關(guān)系:

徹底理解maven
插件
上面我們提到,Maven 將所有項(xiàng)目的構(gòu)建過(guò)程統(tǒng)一抽象成一套生命周期: 項(xiàng)目的清理、初始化、編譯、測(cè)試、打包、集成測(cè)試、驗(yàn)證、部署和站點(diǎn)生成 … 幾乎所有項(xiàng)目的構(gòu)建,都能映射到這一組生命周期上. 但生命周期是抽象的(Maven的生命周期本身是不做任何實(shí)際工作), 任務(wù)執(zhí)行(如編譯源代碼)均交由插件完成. 其中每個(gè)構(gòu)建步驟都可以綁定一個(gè)或多個(gè)插件的目標(biāo),而且Maven為大多數(shù)構(gòu)建步驟都編寫并綁定了默認(rèn)插件.當(dāng)用戶有特殊需要的時(shí)候, 也可以配置插件定制構(gòu)建行為, 甚至自己編寫插件.
再說(shuō)生命周期
Maven 擁有三套相互獨(dú)立的生命周期: clean、default 和 site, 而每個(gè)生命周期包含一些phase階段, 階段是有順序的, 并且后面的階段依賴于前面的階段. 而三套生命周期相互之間卻并沒(méi)有前后依賴關(guān)系, 即調(diào)用site周期內(nèi)的某個(gè)phase階段并不會(huì)對(duì)clean產(chǎn)生任何影響.
clean
clean生命周期的目的是清理項(xiàng)目:
執(zhí)行如$ mvn clean;
default
default生命周期定義了真正構(gòu)建時(shí)所需要執(zhí)行的所有步驟:
執(zhí)行如$ mvn clean install;
site
site生命周期的目的是建立和發(fā)布項(xiàng)目站點(diǎn): Maven能夠基于POM所包含的信息,自動(dòng)生成一個(gè)友好的站點(diǎn),方便團(tuán)隊(duì)交流和發(fā)布項(xiàng)目信息
執(zhí)行命令如$ mvn clean deploy site-deploy;
這三個(gè)lifecycle定義了其包含的phase。maven會(huì)在這三個(gè)lifecycle中匹配對(duì)應(yīng)的phase。當(dāng)執(zhí)行某個(gè)phase時(shí),maven會(huì)依次執(zhí)行在這個(gè)phase之前的phase。
插件
生命周期的階段phase與插件的目標(biāo)goal相互綁定, 用以完成實(shí)際的構(gòu)建任務(wù). 而對(duì)于插件本身, 為了能夠復(fù)用代碼,它往往能夠完成多個(gè)任務(wù), 這些功能聚集在一個(gè)插件里,每個(gè)功能就是一個(gè)目標(biāo).
如:$ mvn compiler:compile: 冒號(hào)前是插件前綴, 后面是該插件目標(biāo)(即: maven-compiler-plugin的compile目標(biāo)).
而該目標(biāo)綁定了default生命周期的compile階段: 他們的綁定能夠?qū)崿F(xiàn)項(xiàng)目編譯的目的.
內(nèi)置綁定
為了能讓用戶幾乎不用任何配置就能使用Maven構(gòu)建項(xiàng)目, Maven 默認(rèn)為一些核心的生命周期綁定了插件目標(biāo), 當(dāng)用戶通過(guò)命令調(diào)用生命周期階段時(shí), 對(duì)應(yīng)的插件目標(biāo)就會(huì)執(zhí)行相應(yīng)的邏輯.

徹底理解maven
徹底理解maven
上圖只列出了打包方式為jar且擁有插件綁定關(guān)系的階段(packaging 定義了Maven項(xiàng)目打包方式, 通常打包方式與所生成構(gòu)件擴(kuò)展名對(duì)應(yīng),有jar(默認(rèn))、war、pom、maven-plugin等., 其他打包類型生命周期的默認(rèn)綁定關(guān)系可參考: Built-in Lifecycle Bindings、Plugin Bindings for default Lifecycle Reference.

徹底理解maven
自定義綁定
除了內(nèi)置綁定以外, 用戶還能夠自定義將某個(gè)插件目標(biāo)綁定到生命周期的某個(gè)階段上. 如創(chuàng)建項(xiàng)目的源碼包, maven-source-plugin插件的jar-no-fork目標(biāo)能夠?qū)㈨?xiàng)目的主代碼打包成jar文件, 可以將其綁定到verify階段上:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.0</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

executions下每個(gè)execution子元素可以用來(lái)配置執(zhí)行一個(gè)任務(wù).
聚合與繼承
Maven的聚合特性(aggregation)能夠使項(xiàng)目的多個(gè)模塊聚合在一起構(gòu)建, 而繼承特性(inheritance)能夠幫助抽取各模塊相同的依賴、插件等配置,在簡(jiǎn)化模塊配置的同時(shí), 保持各模塊一致.
模塊聚合
隨著項(xiàng)目越來(lái)越復(fù)雜(需要解決的問(wèn)題越來(lái)越多、功能越來(lái)越重), 我們更傾向于將一個(gè)項(xiàng)目劃分幾個(gè)模塊并行開發(fā), 如: 將feedcenter-push項(xiàng)目劃分為client、core和web三個(gè)模塊, 而我們又想一次構(gòu)建所有模塊, 而不是針對(duì)各模塊分別執(zhí)行$ mvn命令. 于是就有了Maven的模塊聚合 -> 將feedcenter-push作為聚合模塊將其他模塊聚集到一起構(gòu)建:
聚合POM
聚合模塊POM僅僅是幫助聚合其他模塊構(gòu)建的工具, 本身并無(wú)實(shí)質(zhì)內(nèi)容:

<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/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.vdian.feedcenter</groupId>
    <artifactId>feedcenter-push</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0.SNAPSHOT</version>

    <modules>
        <module>feedcenter-push-client</module>
        <module>feedcenter-push-core</module>
        <module>feedcenter-push-web</module>
    </modules>

</project>

通過(guò)在一個(gè)打包方式為pom的Maven項(xiàng)目中聲明任意數(shù)量的module以實(shí)現(xiàn)模塊聚合:

packaging: pom, 否則無(wú)法聚合構(gòu)建.
modules: 實(shí)現(xiàn)聚合的核心,module值為被聚合模塊相對(duì)于聚合POM的相對(duì)路徑, 每個(gè)被聚合模塊下還各自包含有pom.xml、src/main/java、src/test/java等內(nèi)容, 離開聚合POM也能夠獨(dú)立構(gòu)建

若<packaging>元素的內(nèi)容是jar,那么我們很好理解,也就是說(shuō)這個(gè)項(xiàng)目最終會(huì)被打包成一個(gè)jar包,那<packaging>元素為pom又是什么意思呢?從字面上的意思來(lái)看,這個(gè)項(xiàng)目將打包成一個(gè)pom。我們不妨去maven倉(cāng)庫(kù)里去瞧瞧(前提是已經(jīng)在項(xiàng)目下運(yùn)行了mvn install命令)??梢园l(fā)現(xiàn)這個(gè)文件其實(shí)和項(xiàng)目中的pom.xml是同一個(gè)文件,這樣做的目的是什么呢?上面我們說(shuō)過(guò)PO對(duì)象也是有繼承關(guān)系的,<packaging>pom</packaging>的作用就在這里,這就是maven中project inheritance的概念。當(dāng)實(shí)際執(zhí)行maven命令的時(shí)候,會(huì)根據(jù)project inheritance關(guān)系對(duì)項(xiàng)目的pom.xml進(jìn)行轉(zhuǎn)化,得到真正執(zhí)行時(shí)所用到的pom.xml,即所謂的effective pom,因此可以得到一個(gè)結(jié)論:所有<packaging>元素為pom的項(xiàng)目其實(shí)并不會(huì)輸出一個(gè)可供外部使用,類似于jar包的東西。這類項(xiàng)目的作用有兩個(gè):
管理子項(xiàng)目

例如這里的api和biz就是echo項(xiàng)目的兩個(gè)module。若沒(méi)有echo這個(gè)父項(xiàng)目,我們需要到api和biz兩個(gè)項(xiàng)目下分別執(zhí)行mvn install命令才能完成整個(gè)構(gòu)建過(guò)程,而有了echo這個(gè)父項(xiàng)目之后,我們只需在echo項(xiàng)目中執(zhí)行mvn install即可,maven會(huì)解析pom.xml,發(fā)現(xiàn)該項(xiàng)目有api和biz兩個(gè)module,它會(huì)分別到這兩個(gè)項(xiàng)目下去執(zhí)行mvn install命令。當(dāng)module數(shù)量比較多的時(shí)候,能大大提高構(gòu)建的效率。
管理繼承屬性

比如A和B都需要某個(gè)依賴,那么在父類項(xiàng)目的pom.xml中聲明即可,因?yàn)楦鶕?jù)PO對(duì)象的繼承關(guān)系,A和B項(xiàng)目會(huì)繼承父類項(xiàng)目的依賴,這樣就可以減少一些重復(fù)的輸入。

effective pom包含了當(dāng)前項(xiàng)目的PO對(duì)象,直到Super POM對(duì)應(yīng)的PO對(duì)象中的信息。要看一個(gè)項(xiàng)目的effective pom,只需在項(xiàng)目中執(zhí)行
mvn help:effective-pom
命令即可查看。這里順帶說(shuō)一句,有的同學(xué)可能不理解上面這個(gè)命令是什么意思。maven命令的語(yǔ)法為
mvn [options] [goal(s)] [phase(s)]
goal和phase。maven允許你執(zhí)行一個(gè)或者多個(gè)goals/phases。很明顯這面的命令help:effective-pom并不是一個(gè)phase,那么也就是說(shuō)它是一個(gè)goal。對(duì)這個(gè)goal只不過(guò)是采用了縮寫的形式,其全稱是這樣的:

org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom

以分號(hào)為分隔符,包含了groupId,artifactId,version,goal四部分。若groupId為org.apache.maven.plugins則可以使用上述的簡(jiǎn)寫形式。也就是說(shuō)

mvn help:effective-pom
mvn org.apache.maven.plugins:maven-help-plugin:2.2:effective-pom

是等價(jià)的,都是執(zhí)行了maven-help-plugin這個(gè)plugin中的effective-pom這個(gè)goal。
我們知道一個(gè)plugin中可以包含多個(gè)goal,goal可以綁定到lifecycle中的某一個(gè)phase,這樣在執(zhí)行這個(gè)phase的時(shí)候就會(huì)調(diào)用該goal。那那些沒(méi)有綁定到phase上的goal應(yīng)該如何執(zhí)行呢?這就是 mvn [goal(s)]
這里的goal也就是官方文檔中所說(shuō)的standalone goal,也就是說(shuō)若一個(gè)plugin中的某個(gè)goal沒(méi)有和一個(gè)phase進(jìn)行綁定,可以通過(guò)這種方式來(lái)執(zhí)行??赡苡械淖x者使用過(guò)

mvn dependency:tree

這條命令,這里其實(shí)就是單獨(dú)執(zhí)行一個(gè)goal,這個(gè)goal的作用是分析該工程的依賴并使用樹狀的形式打印出來(lái)。這里的dependency:tree其實(shí)是一個(gè)簡(jiǎn)寫的形式,其完×××式是:

mvn org.apache.maven.plugins:maven-dependency-plugin:<版本號(hào)信息>:tree

也就是說(shuō)單獨(dú)執(zhí)行一個(gè)goal的方式是:
mvn <groupId>:<artifactId>:<version>:<goal>
每次都要敲這么長(zhǎng)一串命令是很繁瑣的,因此才有了上述的簡(jiǎn)寫的形式。maven規(guī)定了對(duì)于plugin的artifactId是如下兩種形式:
maven-{prefix}-maven-plugin
的可以使用簡(jiǎn)寫的方式${prefix}來(lái)表示一個(gè)plugin.
模塊繼承
在面向?qū)ο笾? 可以通過(guò)類繼承實(shí)現(xiàn)復(fù)用. 在Maven中同樣也可以創(chuàng)建POM的父子結(jié)構(gòu), 通過(guò)在父POM中聲明一些配置供子POM繼承來(lái)實(shí)現(xiàn)復(fù)用與消除重復(fù)
父pom
與聚合類似, 父POM的打包方式也是pom, 因此可以繼續(xù)復(fù)用聚合模塊的POM(這也是在開發(fā)中常用的方式):

<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/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.vdian.feedcenter</groupId>
    <artifactId>feedcenter-push</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0.SNAPSHOT</version>

    <modules>
        <module>feedcenter-push-client</module>
        <module>feedcenter-push-core</module>
        <module>feedcenter-push-web</module>
    </modules>

    <properties>
        <finalName>feedcenter-push</finalName>
        <warName>${finalName}.war</warName>
        <spring.version>4.0.6.RELEASE</spring.version>
        <junit.version>4.12</junit.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <warExplodedDirectory>exploded/${warName}</warExplodedDirectory>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring.version}</version>
            </dependency>

           <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>3.0.0</version>
                    <executions>
                        <execution>
                            <id>attach-sources</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>jar-no-fork</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

dependencyManagement: 能讓子POM繼承父POM的配置的同時(shí), 又能夠保證子模塊的靈活性: 在父POMdependencyManagement元素配置的依賴聲明不會(huì)實(shí)際引入子模塊中, 但能夠約束子模塊dependencies下的依賴的使用(子模塊只需配置groupId與artifactId, 見下).
pluginManagement: 與dependencyManagement類似, 配置的插件不會(huì)造成實(shí)際插件的調(diào)用行為, 只有當(dāng)子POM中配置了相關(guān)plugin元素, 才會(huì)影響實(shí)際的插件行為.

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>com.vdian.feedcenter</groupId>
        <artifactId>feedcenter-push</artifactId>
        <version>1.0.0.SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>feedcenter-push-client</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

元素繼承
可以看到, 子POM中并未定義模塊groupId與version, 這是因?yàn)樽覲OM默認(rèn)會(huì)從父POM繼承了如下元素:

groupId、version
dependencies
developers and contributors
plugin lists (including reports)
plugin executions with matching ids
plugin configuration
resources 

因此所有的springframework都省去了version、junit還省去了scope, 而且插件還省去了executions與configuration配置, 因?yàn)橥暾穆暶饕呀?jīng)包含在父POM中.
優(yōu)勢(shì): 當(dāng)依賴、插件的版本、配置等信息在父POM中聲明之后, 子模塊在使用時(shí)就無(wú)須聲明這些信息, 也就不會(huì)出現(xiàn)多個(gè)子模塊使用的依賴版本不一致的情況, 也就降低了依賴沖突的幾率. 另外如果子模塊不顯式聲明依賴與插件的使用, 即使已經(jīng)在父POM的dependencyManagement、pluginManagement中配置了, 也不會(huì)產(chǎn)生實(shí)際的效果.
推薦: 模塊繼承與模塊聚合同時(shí)進(jìn)行,這意味著, 你可以為你的所有模塊指定一個(gè)父工程, 同時(shí)父工程中可以指定其余的Maven模塊作為它的聚合模塊. 但需要遵循以下三條規(guī)則:

在所有子POM中指定它們的父POM;
將父POM的packaging值設(shè)為pom;
在父POM中指定子模塊/子POM的目錄.

parent元素內(nèi)還包含一個(gè)relativePath元素, 用于指定父POM的相對(duì)路徑, 默認(rèn)../pom.xml
超級(jí)pom-約定優(yōu)先于配置
任何一個(gè)Maven項(xiàng)目都隱式地繼承自超級(jí)POM, 因此超級(jí)POM的大量配置都會(huì)被所有的Maven項(xiàng)目繼承, 這些配置也成為了Maven所提倡的約定.

<!-- START SNIPPET: superpom -->
<project>
  <modelVersion>4.0.0</modelVersion>

  <!-- 定義了中央倉(cāng)庫(kù)以及插件倉(cāng)庫(kù), 均為:https://repo.maven.apache.org/maven2 -->
  <repositories>
    <repository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
      <releases>
        <updatePolicy>never</updatePolicy>
      </releases>
    </pluginRepository>
  </pluginRepositories>

  <!-- 依次定義了各類代碼、資源、輸出目錄及最終構(gòu)件名稱格式, 這就是Maven項(xiàng)目結(jié)構(gòu)的約定 -->
  <build>
    <directory>${project.basedir}/target</directory>
    <outputDirectory>${project.build.directory}/classes</outputDirectory>
    <finalName>${project.artifactId}-${project.version}</finalName>
    <testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
    <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
    <scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
    <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>${project.basedir}/src/test/resources</directory>
      </testResource>
    </testResources>

    <!-- 為核心插件設(shè)定版本 -->
    <pluginManagement>
      <!-- NOTE: These plugins will be removed from future versions of the super POM -->
      <!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
      <plugins>
        <plugin>
          <artifactId>maven-antrun-plugin</artifactId>
          <version>1.3</version>
        </plugin>
        <plugin>
          <artifactId>maven-assembly-plugin</artifactId>
          <version>2.2-beta-5</version>
        </plugin>
        <plugin>
          <artifactId>maven-dependency-plugin</artifactId>
          <version>2.8</version>
        </plugin>
        <plugin>
          <artifactId>maven-release-plugin</artifactId>
          <version>2.3.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <!-- 定義項(xiàng)目報(bào)告輸出路徑 -->
  <reporting>
    <outputDirectory>${project.build.directory}/site</outputDirectory>
  </reporting>

  <!-- 定義release-profile, 為構(gòu)件附上源碼與文檔 -->
  <profiles>
    <!-- NOTE: The release profile will be removed from future versions of the super POM -->
    <profile>
      <id>release-profile</id>

      <activation>
        <property>
          <name>performRelease</name>
          <value>true</value>
        </property>
      </activation>

      <build>
        <plugins>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-source-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-sources</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-javadoc-plugin</artifactId>
            <executions>
              <execution>
                <id>attach-javadocs</id>
                <goals>
                  <goal>jar</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <inherited>true</inherited>
            <artifactId>maven-deploy-plugin</artifactId>
            <configuration>
              <updateReleaseInfo>true</updateReleaseInfo>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

</project>
<!-- END SNIPPET: superpom -->

Maven Plugin 開發(fā)
詳細(xì)代碼在maven plugin demo

創(chuàng)建plugin項(xiàng)目

mvn archetype:generate 
    -DgroupId=com.fq.plugins -DartifactId=lc-maven-plugin -Dversion=0.0.1-SNAPSHOT
    -DarchetypeArtifactId=maven-archetype-plugin 
    -DinteractiveMode=false 
    -DarchetypeCatalog=internal

使用maven-archetype-plugin Archetype可以快速創(chuàng)建一個(gè)Maven插件項(xiàng)目。
pom.xml
插件本身也是Maven項(xiàng)目, 特殊之處在于packaging方式為maven-plugin:

<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.fq.plugins</groupId>
    <artifactId>lc-maven-plugins</artifactId>
    <packaging>maven-plugin</packaging>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.3</version>
        </dependency>
    </dependencies>

</project>

maven-plugin 打包方式能控制Maven為其在生命周期階段綁定插件處理的相關(guān)目標(biāo).

編寫目標(biāo)Mojo

@Mojo(name = "lc", defaultPhase = LifecyclePhase.VERIFY)
public class LCMavenMojo extends AbstractMojo {

    private static final List<String> DEFAULT_FILES = Arrays.asList("java", "xml", "properties");

    @Parameter(defaultValue = "${project.basedir}", readonly = true)
    private File baseDir;

    @Parameter(defaultValue = "${project.build.sourceDirectory}", readonly = true)
    private File srcDir;

    @Parameter(defaultValue = "${project.build.testSourceDirectory}", readonly = true)
    private File testSrcDir;

    @Parameter(defaultValue = "${project.build.resources}", readonly = true)
    private List<Resource> resources;

    @Parameter(defaultValue = "${project.build.testResources}", readonly = true)
    private List<Resource> testResources;

    @Parameter(property = "lc.file.includes")
    private Set<String> includes = new HashSet<>();

    private Log logger = getLog();

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        if (includes.isEmpty()) {
            logger.debug("includes/lc.file.includes is empty!");
            includes.addAll(DEFAULT_FILES);
        }
        logger.info("includes: " + includes);

        try {
            long lines = 0;
            lines += countDir(srcDir);
            lines += countDir(testSrcDir);

            for (Resource resource : resources) {
                lines += countDir(new File(resource.getDirectory()));
            }
            for (Resource resource : testResources) {
                lines += countDir(new File(resource.getDirectory()));
            }

            logger.info("total lines: " + lines);
        } catch (IOException e) {
            logger.error("error: ", e);
            throw new MojoFailureException("execute failure: ", e);
        }
    }

    private LineProcessor<Long> lp = new LineProcessor<Long>() {

        private long line = 0;

        @Override
        public boolean processLine(String fileLine) throws IOException {
            if (!Strings.isNullOrEmpty(fileLine)) {
                ++this.line;
            }
            return true;
        }

        @Override
        public Long getResult() {
            long result = line;
            this.line = 0;
            return result;
        }
    };

    private long countDir(File directory) throws IOException {
        long lines = 0;
        if (directory.exists()) {
            Set<File> files = new HashSet<>();
            collectFiles(files, directory);

            for (File file : files) {
                lines += CharStreams.readLines(new FileReader(file), lp);
            }

            String path = directory.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
            logger.info("path: " + path + ", file count: " + files.size() + ", total line: " + lines);
            logger.info("\t-> files: " + files.toString());
        }

        return lines;
    }

    private void collectFiles(Set<File> files, File file) {
        if (file.isFile()) {
            String fileName = file.getName();
            int index = fileName.lastIndexOf(".");
            if (index != -1 && includes.contains(fileName.substring(index + 1))) {
                files.add(file);
            }
        } else {
            File[] subFiles = file.listFiles();
            for (int i = 0; subFiles != null && i < subFiles.length; ++i) {
                collectFiles(files, subFiles[i]);
            }
        }
    }
}

@Parameter: 配置點(diǎn), 提供Mojo的可配置參數(shù). 大部分Maven插件及其目標(biāo)都是可配置的, 通過(guò)配置點(diǎn), 用戶可以自定義插件行為

<plugin>
    <groupId>com.fq.plugins</groupId>
    <artifactId>lc-maven-plugins</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <executions>
        <execution>
            <id>lc</id>
            <phase>verify</phase>
            <goals>
                <goal>lc</goal>
            </goals>
            <configuration>
                <includes>
                    <include>java</include>
                    <include>lua</include>
                    <include>json</include>
                    <include>xml</include>
                    <include>properties</include>
                </includes>
            </configuration>
        </execution>
    </executions>
</plugin>

execute(): 實(shí)際插件功能;
異常: execute()方法可以拋出以下兩種異常:
MojoExecutionException: Maven執(zhí)行目標(biāo)遇到該異常會(huì)顯示 BUILD FAILURE 錯(cuò)誤信息, 表示在運(yùn)行期間發(fā)生了預(yù)期的錯(cuò)誤;
MojoFailureException: 表示運(yùn)行期間遇到了未預(yù)期的錯(cuò)誤, 顯示 BUILD ERROR 信息

測(cè)試&執(zhí)行

通過(guò)mvn clean install將插件安裝到倉(cāng)庫(kù)后, 就可將其配置到實(shí)際Maven項(xiàng)目中, 用于統(tǒng)計(jì)項(xiàng)目代碼了:

$ mvn com.fq.plugins:lc-maven-plugins:0.0.1-SNAPSHOT:lc 

你可能注意到為了調(diào)用該插件的goal,我們需要給出該插件的所有坐標(biāo)信息,包裹groupId, artifactId,version號(hào),你可能之前已經(jīng)執(zhí)行過(guò)"mvn eclipase:eclipase"或"mvn idea:idea"這樣簡(jiǎn)潔的命令,讓我們也來(lái)將自己的插件調(diào)用變簡(jiǎn)單一點(diǎn)。要通過(guò)簡(jiǎn)單別名的方式調(diào)用Maven插件,我們需要做到以下兩點(diǎn):

插件的artifactId應(yīng)該遵循-maven-plugin或maven--plugin命名規(guī)則,對(duì)于本文中的插件,我們已經(jīng)遵循了。
需要將插件的groupId放在Maven默認(rèn)的插件搜尋范圍之內(nèi),默認(rèn)情況下Maven只會(huì)在org.apache.maven.plugins和org.codehaus.mojo兩個(gè)groupId下搜索插件,要讓Maven同時(shí)搜索我們自己的groupId,我們需要在~/.m2/settings.xml中加入:

<pluginGroups>  
       <pluginGroup>com.fq.plugins</pluginGroup>  
</pluginGroups>

在達(dá)到以上兩點(diǎn)之后,我們便可以通過(guò)以下命令來(lái)調(diào)用自己的插件了:
mvn lc:lc

要在別的項(xiàng)目中應(yīng)用插件也是簡(jiǎn)單的,我們只需要在該項(xiàng)目的pom.xml文件中使用上面<plugin>標(biāo)簽聲明該插件即可。

當(dāng)前題目:徹底理解maven
本文地址:http://www.muchs.cn/article14/pdhpde.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、企業(yè)建站、自適應(yīng)網(wǎng)站域名注冊(cè)、營(yíng)銷型網(wǎng)站建設(shè)、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

手機(jī)網(wǎng)站建設(shè)