聊一聊maven的內部運行原理

前言

  maven是我們現在最常使用的一個開發者使用工具之一,作爲使用最頻繁的工具之一,我們對於maven真正的瞭解嗎?本篇文章咱們來着重瞭解了Maven的內部運行原理。


敘述

  Apache Maven,是一個軟件(特別是Java軟件)項目管理及自動構建工具,由Apache軟件基金會所提供。基於項目對象模型(縮寫:POM)概念,Maven利用一箇中央信息片斷能管理一個項目的構建、報告和文檔等步驟。(維基百科)

首先我們通過一張思維導圖大概的瞭解一下maven。
在這裏插入圖片描述


Why

Maven解決了什麼問題?

  
① 一個項目就是一個工程

如果項目非常龐大,就不適合使用package來劃分模塊,最好是每一個模塊對應一個工程,利於分工協作。藉助於maven就可以將一個項目拆分成多個工程

② 項目中使用jar包,需要“複製”、“粘貼”項目的lib中

同樣的jar包重複的出現在不同的項目工程中,你需要做不停的複製粘貼的重複工作。藉助於maven,可以將jar包保存在“倉庫”中,不管在哪個項目只要使用引用即可就行。

③ jar包需要的時候每次都要自己準備好或到官網下載

藉助於maven我們可以使用統一的規範方式下載jar包,規範

④ jar包版本不一致的風險

不同的項目在使用jar包的時候,有可能會導致各個項目的jar包版本不一致,導致未執行錯誤。藉助於maven,所有的jar包都放在“倉庫”中,所有的項目都使用倉庫的一份jar包。

⑤ 一個jar包依賴其他的jar包需要自己手動的加入到項目中

FileUpload組件->IO組件,commons-fileupload-1.3.jar依賴於commons-io-2.0.1.jar

極大的浪費了我們導入包的時間成本,也極大的增加了學習成本。藉助於maven,它會自動的將依賴的jar包導入進來。

Maven好處

  
1.優秀的構建工具
2.管理項目信息
3.提供免費的中央倉庫
。。。


What

本質

  Maven的本質是工具。

原理

  在瞭解Maven的運行原理之前,我們先來看一下Maven中的相關概念。

POM

  Maven項目使用項目對象模型(Project Object Model,POM)來配置。項目對象模型存儲在名爲 pom.xml 的文件中。

 POM: 注意這裏的POM不是maven中構建過程使用的配置文件pom.xml,但他們之間還是有聯繫的。POM的全稱是Project Object Model,用通俗點的話說就是對要構建的項目進行建模,將要構建的項目看成是一個對象(Object),後文就使用PO來指代這個對象。既然是一個對象,那麼PO有哪些屬性呢?在maven中一個項目都是用一個唯一的座標(coordinate)來表示,座標由groupId, artifactId, version, classifier, type這五部分組成。這樣來說PO應該也要具備座標屬性。另外一方面,一個項目肯定不是孤立存在的,可能依賴於其他的一些項目,那麼也就是說PO應該具備dependencies這個屬性,用來表示其所依賴的外部項目。我們可以嘗試一下用Java代碼來描述下PO這個對象:

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

  我們知道XML的表達能力是很強大的,一個Java中的對象是可以用XML來描述,例如一個上面定義的PO對象則可以用下面的XML來描述(表達有不規範之處,理解其中的含義即可):

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

  是不是好像和什麼有點類似?對,就是pom.xml。pom.xml就是PO對象的XML描述。上面的PO定義還不完整,我們繼續看下PO還有什麼其他的屬性。我們知道在Java中類是可以繼承的,一個對象在創建的時候會同時創建其父類對象,類似的,PO對象也有其父對象,用parent屬性來表示,並且PO對象會繼承其父對象的所有屬性。另外一方面,一個項目可能根據不同職責分爲多個模塊(module),所有模塊其實也就是一個單獨的項目,只不過這些項目會使用其父對象的一些屬性來進行構建。我們將這些新的屬性加到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;
}

  再將這個定義用XML語言表示一下:

<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的由來。再說一遍:pom.xml就是PO對象的XML描述


Lifecycle

  上面說的這些PO屬性是項目的一些固有屬性,到目前爲止,我們還沒有涉及項目的構建過程。構建過程對應的是PO對象的build屬性,那麼你應該馬上想到,在pom.xml中就是元素中的內容。這裏就有引入maven中第二個核心概念:Lifecycle。Lifecycle直譯過來就是生命週期。我們平常會接觸到哪些週期呢?一年中春夏秋冬就是一個週期。一個週期中可能分爲多個階段,比如這裏的春夏秋冬。在maven中一個構建過程就對應一個Lifecycle,這個Lifecycle也分爲多個階段,每個階段叫做phase。你可能會問,那這個Lifecycle中包含多少個phase呢?一個標準的構建Lifecycle包含了如下的phase:

validate: 用於驗證項目的有效性和其項目所需要的內容是否具備
initialize:初始化操作,比如創建一些構建所需要的目錄等。
generate-sources:用於生成一些源代碼,這些源代碼在compile phase中需要使用到
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:集成測試後置操作
install:將打包產物安裝到本地maven倉庫
deploy:將打包產物安裝到遠程倉庫

  在maven中,你執行任何一個phase時,maven會將其之前的phase都執行。例如 mvn install,那麼maven會將deploy之外的所有phase按照他們出現的順序一次執行。

  Lifecycle還牽涉到另外一個非常重要的概念:goal。注意上面Lifecycle的定義,也就是說maven爲程序的構建定義了一套規範流程:第一步需要validate,第二步需要initialize… … compile,test,package,… … install,deploy,但是並沒有定義每一個phase具體應該如何操作。這裏的phase的作用有點類似於Java語言中的接口,只協商了一個契約,但並沒有定義具體的動作。比如說compile這個phase定義了在構建流程中需要經過編譯這個階段,但沒有定義應該怎麼編譯(編譯的輸入是什麼?用什麼編譯javac/gcc?)。這裏具體的動作就是由goal來定義,一個goal在maven中就是一個Mojo(Maven old java object)。Mojo抽象類中定義了一個execute()方法,一個goal的具體動作就是在execute()方法中實現。實現的Mojo類應該放在哪裏呢?答案是maven plugin裏,所謂的plugin其實也就是一個maven項目,只不過這個項目會引用maven的一些API,plugin項目也具備maven座標。
  在執行具體的構建時,我們需要爲lifecycle的每個phase都綁定一個goal,這樣才能夠在每個步驟執行一些具體的動作。比如在lifecycle中有個compile phase規定了構建的流程需要經過編譯這個步驟,而maven-compile-plugin這個plugin有個compile goal就是用javac來將源文件編譯爲class文件的,我們需要做的就是將compile這個phase和maven-compile-plugin中的compile這個goal進行綁定,這樣就可以實現Java源代碼的編譯了。有點繞,多看幾遍。那麼有人就會問,在哪裏綁定呢?答案是在pom.xml元素中配置即可。例如:

<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這個goal綁定到了test這個phase,後續在maven執行到test phase時就會執行query goal。還有有人可能會問,我都沒有指定Java源文件的位置,編譯啥?這就引出了maven的design principle。在maven中,有一個非常著名的principle就是convention over configuration(約定優於配置)。這一點和ant有非常大的區別,例如使用ant來進行編譯時,我們需要指定源文件的位置,輸出文件的位置,javac的位置,classpath… …在maven中這些都是不需要,若沒有手動配置,maven默認從<項目根目錄>/src/main/java這個目錄去查找Java源文件,編譯後的class文件會保存在<項目根目錄>/target/classes目錄。在maven中,所有的PO都有一個根對象,就是Super POM。Super POM中定義了所有的默認的配置項。Super POM對應的pom.xml文件可以在maven安裝目錄下lib/maven-model-builder-3.0.3.jar:org/apache/maven/model/pom-4.0.0.xml中找到。用一張圖來表示maven Lifecycle,phase,goal之間的關係:
在這裏插入圖片描述

  如:$ mvn compiler:compile: 冒號前是插件前綴, 後面是該插件目標(即: maven-compiler-plugin的compile目標).

  而該目標綁定了default生命週期的compile階段:
在這裏插入圖片描述
  因此, 他們的綁定能夠實現項目編譯的目的.


內置綁定

爲了能讓用戶幾乎不用任何配置就能使用Maven構建項目, Maven 默認爲一些核心的生命週期綁定了插件目標, 當用戶通過命令調用生命週期階段時, 對應的插件目標就會執行相應的邏輯.

clean生命週期階段綁定:

生命週期階段 插件目標
pre-clean -
clean maven-clean-plugin:clean
pre-clean -

default聲明週期階段綁定:

生命週期階段 插件目標 執行任務
process-resources maven-resources-plugin:resources 複製主資源文件到主輸出目錄
compile maven-compiler-plugin:compile 編譯主代碼到主輸出目錄
process-test-resources maven-resources-plugin:testResources 複製測試資源文件到測試輸出目錄
test-compile maven-compiler-plugin:testCompile 編譯測試代碼到測試輸出目錄
test maven-surefire-plugin:test 執行測試用例
package maven-jar-plugin:jar 打jar包
install maven-install-plugin:install 將項目輸出安裝到本地倉庫
deploy maven-deploy-plugin:deploy 將項目輸出部署到遠程倉庫

site生命週期階段綁定:

生命週期階段 插件目標
pre-site -
site maven-site-plugin:site
post-site -
site-deploy maven-site-plugin:deploy

到現在是不是就瞭解了爲什麼maven項目中有Plugins這一項內容了吧。
在這裏插入圖片描述


小結

我們要做到知其然而知其所以然。加油!

感謝您的閱讀~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章