本篇介紹Maven,內容皆總結摘抄自《Maven實戰》,僅用作筆記。
Maven簡介
Maven是Apache組織中的一個頗爲成功的開源項目,主要服務於基於Java平臺的項目構建、依賴管理和項目信息管理。
Maven是一個異常強大的構建工具,能夠幫我們自動化構建過程,從清理、編譯、測試到生成報告,再到打包和部署。只需要使用Maven配置好項目,然後輸入命令即可。
Maven通過一個座標系統準確的定位每一個構件(artifact),也就是說通過一組座標Maven能夠找到任何一個Java類庫。
Maven還能幫助我們管理原本分散在項目中各個角落的項目信息,包括項目描述、開發者列表、版本控制系統地址等。
Maven還爲全世界的Java開發者提供了一個免費的中央倉庫,在其中幾乎可以找到任何的流行開源類庫。
安裝和配置
下載Maven
到Maven官網http://maven.apache.org/download.cgi選擇下載後綴爲bin.zip的文件,然後解壓。如下圖:
修改本地倉庫路徑
進入conf目錄,打開settings.xml,找到localRepository元素修改爲要設置爲本地倉庫的目錄,例如:
設置阿里雲鏡像
Maven默認訪問中央倉庫,而中央倉庫地址在國外,因此在下載構件時速度很慢。設置阿里雲鏡像可以有效解決這一問題。
進入conf目錄,打開settings.xml,在mirrors元素中添加以下mirror標籤:
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
設置環境變量
在桌面上右擊“我的電腦”->“屬性”,單擊”高級設置“,再選擇“環境變量”。在系統變量中添加一個變量,變量名爲MAVEN_HOME,變量值爲Maven的解壓目錄。如下圖:
然後在系統變量中找到名爲Path的變量,在變量值的末尾添加“%MAVEN_HOME%\bin;”,如下圖:
配置好環境變量後,打開cmd窗口,輸入“mvn -v”檢查Maven是否安裝成功。如下圖是安裝成功的。
安裝目錄分析
解壓之後的Maven目錄包含如下結構和內容:
- bin:包含了mvn運行的腳本,這些腳本用來配置Java命令,準備好classpath和相關的Java系統屬性,然後執行Java命令。
- boot:包含了plexus-classworlds-2.6.0.jar文件,plexus-classworlds是一個類加載器框架,相對於默認的java類加載器,它提供了更豐富的語法以方便配置,Maven使用該框架加載自己的類庫。
- conf:包含了一個非常重要的文件settings.xml。直接修改該文件,就能在機器上全局的定製Maven的行爲。
- lib:包含了所有Maven運行時需要的Java類庫。此外,還包含一些Maven用到的第三方依賴。可以說,lib目錄就是真正的Maven。
使用入門
Maven項目的核心是pom.xml。POM(Project Object Model,項目對象模型)定義了項目的基本信息,用於描述項目如何構建,聲明項目依賴等等。下面是一個最簡單的pom.xml。
<?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>
<artifactId>project</artifactId>
<groupId>org.galiden</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>module</artifactId>
<name>module</name>
</project>
代碼的第一行是XML頭,指定了該xml文檔的版本和編碼方式。緊接着是project元組,project是所有pom.xml的根元素,它還聲明瞭一些POM相關的命名空間及xsd元素。
根元素下的modelVersion指定了當前POM模型的版本。
groupId、artifactId和version這三個元素定義了一個項目的基本座標。在Maven的世界,任何的jar、pom或者war都是基於這些基本的座標進行區分的。groupId定義了項目屬於哪個組,這個組往往和項目所在的組織或公司存在關聯。artifactId定義了當前Maven項目在族中唯一的ID。version指定了該項目當前的版本---1.0-SNAPSHOT,SNAPSHOT意爲快照,說明該項目還在開發中,是不穩定的版本。
最後一個name元素聲明瞭一個對於用戶更爲友好的項目名稱。
Maven項目中默認的主代碼目錄是src/main/java,對應的,默認的測試代碼目錄是src/test/java。
在這篇文章中介紹了idea如何創建maven項目,我們就使用一個具有多個module的project做演示。其項目結構如下:
在項目根目錄下運行命令“mvn clean compile”會得到以下輸出:
clean告訴Maven清理輸出目錄target/,compile告訴Maben編譯項目主代碼。至此,Maven再沒有任何額外的配置的情況下就執行了項目的清理和編譯任務。
接下來調用Maven執行測試,在使用JUnit單元測試時,要添加JUnit依賴,修改項目的POM代碼如下:
<?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>
<artifactId>project</artifactId>
<groupId>org.galiden</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>module</artifactId>
<name>module</name>
<dependecies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependecies>
</project>
代碼中添加了dependencies元素,該元素下可以包含多個dependency元素以聲明項目的依賴。這裏添加了一個依賴---groupId是junit,artifactId是junit,version是4.12。有了這段聲明,Maven就會自動訪問中央倉庫,下載需要的文件。
上述POM代碼中還有一個值爲test的元素scope,scope爲依賴範圍,若依賴範圍爲test則表示該依賴只對測試有效。換句話說,測試代碼中的import JUnit是沒有問題的,但如果在主代碼中用import JUnit代碼,就會造成編譯錯誤。如果不聲明依賴範圍,默認值是compile,表示該依賴對主代碼和測試代碼都有效。
在項目根目錄執行命令“mvn clean test”。由下圖可以看出任務執行成功了。
將項目進行編譯、測試之後,下一個步驟就是打包。在項目根目錄執行命令“mvn clean package”進行打包,可以看到如下輸出:
類似的,Maven在打包之前執行編譯、測試等操作。打包其實就是jar插件的jar目標將項目主代碼打包成一個名爲module-1.0-SNAPSHOT.jar的文件。該文件也位於target/輸出目錄中。
至此,我們得到了項目的輸出,如果有需要的話,就可以複製這個jar文件到其他項目的Classpath中從而使用App類。但如果要讓其他的Maven項目直接引用這個jar包,還需要一個安裝步驟,執行命令“mvn clean install”安裝jar包。
該任務將項目輸出的jar安裝到了Maven本地倉庫中,只有將類的構件安裝到本地倉庫之後,其他Maven項目才能使用它。
使用Archetype生成項目骨架
在上面生成的Maven項目中有一些Maven的約定,例如在項目的根目錄中放置pom.xml,在src/main/java目錄中放置項目的主代碼,在src/test/java中放置項目的測試代碼。我們稱這些基本的目錄結構和pom.xml文件稱爲項目的骨架。
我們可以使用maven archetype來創建項目的骨架。事實上,上面例子中的project就是筆者在介紹idea時使用骨架創建的一個Maven項目。所以不需要手動創建pom.xml和src/main/java目錄以及src/test/java目錄結構。
座標和依賴
Maven的一大功能是管理項目依賴。爲了能自動化的解析任何一個Java構件,Maven就必須它們唯一標識。這就依賴管理的底層基礎---座標。
Maven的世界中擁有數量巨大的構件,也就是平時用的一些jar、war等文件。在Maven爲這些構件引入座標概念之前,我們無法使用任何一種方式來唯一標識所有這些構件。Maven定義了這樣一組規則:世界上任何一個構件都可以使用Maven座標唯一標識,Maven座標的元素包括groupId、artifactId、version、packaging、classifier。只要我們提供正確的座標元素,Maven就能找到對應的構件。
下面詳細解釋一下各個座標元素:
- groupId:定義當前Maven項目隸屬的實際項目。Maven項目和實際項目不一定是一對一的關係,例如SpringFramework這一實際項目對應的Maven項目會有spring-core、spring-context等。這是由於Maven中模塊的概念,因此,一個實際項目往往會被劃分爲很多模塊。其次,groupId不應該對應項目隸屬的組織或公司,因爲一個組織下會有很多實際項目,如果groupId只定義到組織級別,由於artifactId只能定義Maven項目,那麼實際項目這個層將難以定義。
- artifactId:定義實際項目中的一個Maven項目,推薦的做法是使用實際項目名稱作爲artifactId的前綴。
- version:定義了Maven項目當前所處的版本。
- packaging:定義Maven項目的打包方式。打包方式通常與所生成構件的文件擴展名對應,例如打包方式爲jar包,最終的文件名的後綴爲.jar。當不定義packaging的時候,Maven會使用默認值jar。
- classifier:用來幫助定義構建輸出的一些附屬構件。
在上述5個元素中,groupId、artifactId和version是必須定義的,packaging是可選的,classifier是不可直接定義的。
根元素project下的dependencies可以包含一個或多個dependency元素,以聲明一個或多個項目依賴。每個依賴可以包含的元素有:
- groupId、artiafactId和version:依賴的基本座標,對於任何一個依賴來說,基本座標是最重要的,Maven根據座標才能找到需要的依賴。
- type:依賴的類型,對應於項目座標定義的packaging,默認值爲jar。
- scope:依賴的範圍。
- optional:標記依賴是否可選。
- exclusions:用來排除傳遞性依賴。
倉庫
座標和依賴是任何一個構件在Maven世界中的邏輯表示方式;而構件的物理表示方式是文件,Maven通過倉庫來統一管理這些文件。
在Maven世界中,任何一個依賴、插件或項目構建的輸出,都可以稱爲構件。得益於座標機制,任何Maven項目使用任何一個構件的方式都是完全相同的。在此基礎上,Maven可以在某個位置統一存儲所有Maven項目共享的構件,這個統一的位置就是倉庫。實際的Maven項目將不再各自存儲其依賴文件,只需要聲明這些依賴的座標,在需要的時候,Maven會自動根據座標找到倉庫中的構件,並使用它們。
對於Maven來說,倉庫只分爲兩類:本地倉庫和遠程倉庫。當Maven根據座標尋找構件時,它首先會查看本地倉庫,如果本地倉庫存在此構件,則直接使用;否則,Maven就會去遠程倉庫查找,發現需要的構件之後,下載到本地倉庫再使用。如果本地倉庫和遠程倉庫都沒有需要的構件,就會報錯。
在這個最基本分類的基礎上,還有一些特殊的遠程倉庫。中央倉庫是Maven核心自帶的遠程倉庫,它包含了絕大部分開源的構件。在默認配置下,當本地倉庫沒有Maven需要的構件時,就會嘗試從中央倉庫下載。
私服是另一種特殊的遠程倉庫,爲了節省帶寬和時間,應該在局域網內架設一個私有的倉庫服務器,用其代理所有外部的遠程倉庫。
使用Maven進行日常開發時,一個常見的問題就是如何尋找需要的依賴,我們可能只知道需要使用類庫的項目名稱,但添加Maven依賴需要提供確切的Maven座標。這是可以使用倉庫搜索服務來根據關鍵字得到Maven座標。
筆者使用的是一個叫做Maven Repository的倉庫搜索服務,網址爲https://mvnrepository.com/。例如搜索junit的座標,如下圖:
選擇第2個之後如下圖:
我們可以根據使用次數來選擇相對來說可能更“穩定”的那個版本,選擇4.12版本後出現如下圖:
將紅框中的內容複製到pom.xml的dependencies元素中即可。
生命週期和插件
Maven的生命週期是爲了對所有的構建過程進行抽象和統一,包含了項目的清理、初始化、編譯、測試、打包、集成測試、驗證、部署和站點生成等幾乎所有構建步驟。也就是說,幾乎所有項目的構建,都能映射到這樣一個生命週期上。
Maven的生命週期是抽象的,這意味着生命週期本身不做任何實際的工作,在Maven的設計中,實際的任務都交由插件來完成。生命週期抽象了構建的各個步驟,定義了它們的次序,但沒有提供具體實現。因此Maven設計了插件機制,每個構建步驟都可以綁定一個或者多個插件行爲,而且Maven爲大多數構建步驟編寫並綁定了默認插件。
聚合與繼承
當Maven應用到實際項目中的時候,需要將項目分成不同的模塊。Maven的聚合特性能夠把項目的各個模塊聚合在一起構建,而Maven的繼承特性則能幫助抽取各模塊相同的依賴和插件配置,在簡化POM的同時,還能促進各個模塊配置的一致性。
聚合
當一個實際項目擁有多個Maven項目時,我們會想要一次構建多個項目,而不是到多個模塊的目錄下執行mvn命令。Maven聚合特性就是爲該需求服務的。
爲了使用一條命令就能構建多個模塊,我們需要創建一個額外的模塊A,然後通過該模塊構建整個項目的所有模塊。A本身作爲一個Maven項目,它必須要有自己的POM,但作爲一個聚合項目,其POM又有特殊的地方。其第一個特殊的地方在packaging元素,值爲pom。對於聚合模塊來說,其打包方式packaging的值必須爲pom,否則就無法構建。
實現聚合最核心的配置是module元素。用戶可以通過在一個打包方式爲pom的Maven項目中聲明任意數量的module元素來實現模塊的聚合。這裏的每個module的值都是一個當前POM的相對目錄。爲方便用戶構建項目,通常將聚合模塊放在項目目錄的最頂層,其他模塊作爲聚合模塊的子目錄存在。
繼承
在多模塊Maven項目中,他們的pom.xml可能會存在大量的重複,而重複往往就意味着更多的勞動和更多的潛在的問題。類似於面向對象設計中的父子結構,我們也可以創建POM的父子結構,然後在父POM中聲明一些配置供子POM繼承,以實現“一處聲明,多處使用”的目的。
作爲父模塊的POM,其打包類型也必須爲pom。另外,由於父模塊只是爲了幫助消除配置的重複,因此它本身不包含除POM之外的項目文件,也就不需要src/main/java之類的文件夾了。
有了父模塊,就需要讓其他模塊來繼承它。子模塊中增加parent元素來聲明父模塊,parent下的子元素groupId、artifactId和version指定了父模塊的座標,這三個元素是必須的。元素relativePath表示父模塊POM的相對路徑。在前面“使用入門”中舉例的project就是一個父模塊,而module是一個子模塊。project的POM的部分內容如下:
<groupId>org.galiden</groupId>
<artifactId>project</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>module</module>
</modules>
<name>project</name>
可以看出其packaging的值爲pom且包含一個子模塊module。而子模塊module的POM部分內容如下:
<parent>
<artifactId>project</artifactId>
<groupId>org.galiden</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
聲明瞭其父模塊project的座標。
idea配置Maven
使用Ctrl + Alt + s或在上方菜單欄選擇File->Settings打開Settings,找到Maven,如下圖:
eclipse配置Maven
在上方菜單欄選擇Window->Preference,找到Maven->Installations,如下圖:
點擊Add,選擇要配置的Maven目錄,如下圖:
找到Maven->User Settings,如下圖:
設置完成後本地倉庫目錄修改爲之前在settings.xml文件中設置的目錄: