目標
主要介紹maven的基本概念和工作機制,基於Maven實戰
座標和依賴
依賴管理的基礎是座標,maven倉庫也基於maven座標管理
座標
- maven的座標包括groupId、artifactId、version、packaging、classifier,前三個必須,packaging可選,classifier不能直接定義
- groupId,定義當前項目隸屬的實際項目。groupId不應該對應項目隸屬的公司或者組織,格式爲域名反向格式。
- artifactId,定義實際項目中的一個Maven項目(模塊)。默認情況下,Maven生成的構件,會以artifactId作爲開頭,因此建議artifactId以實際項目爲前綴
- version,該元素定義Maven項目當前所處的版本
- packaging,該元素定義Maven項目的打包方式,打包方式通常與所生成構件的文件擴展名對應,其次打包會影響到構件的生命週期。默認值爲jar
- classifier,該元素用來幫助定義構件輸出的一些附屬構件。附屬構件與主構件對應,比如同時生成包含javadoc,sources的jar包就是附屬構件,這時javadoc和classifier就是這兩個附屬構件的classifier。classifier不能直接在項目部分定義,而是在插件部分定義。因爲附屬構件是插件生成的。
- 一般項目構件的文件名爲artifactId-version[-classifier].packaging
- mvn clean install會將構件安裝到本地倉庫,默認就在本地的~/.m2/repository目錄下
依賴
依賴聲明可以包含如下內容:
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
<exclusion>
<exclusions>
</dependency>
</dependencies>
- 元素位於元素下,下面可以包含多個元素
- 每個元素包含的內容有:
- groupId, artifactId, version作爲依賴的基本座標,用於定位依賴
- type,依賴的類型,對應於packaging,默認爲jar
- scope,依賴的範圍
- optional,標記依賴是否可選
- exclusions,用來排除傳遞性依賴
依賴範圍
- maven在編譯項目的時候使用一套classpath,在編譯和執行測試的時候使用另外一套classpath,在實際運行的時候,會另外再使用一套classpath。依賴範圍用來控制這三種classpath,因此有如下幾種依賴範圍
- compile,編譯依賴範圍,默認值。使用此依賴範圍的依賴對於編譯、測試、運行三種classpath都有效
- test,測試依賴範圍,使用此依賴範圍的依賴只對於測試classpath有效。典型的就是Junit依賴一般聲明爲測試依賴範圍。
- provided,已提供依賴範圍,使用此依賴範圍的依賴,對於編譯和測試classpath有效,但在運行時無效,典型的例子是servlet-api,編譯和測試的時候需要,但運行時由容器提供。
- runtime,運行時依賴範圍,使用此依賴範圍的依賴,對於測試和運行classpath有效,但在編譯主代碼是無效。典型的例子是JDBC驅動實現,項目主代碼的編譯僅需要JDK的JDBC接口,只有在測試和運行時才需要實現。
- system,系統依賴範圍,該依賴與三種classpath的關係與provided依賴範圍完全一致,但是使用system依賴範圍的依賴必須通過systemPath元素顯式的指定依賴文件的路徑。
- import,導入依賴範圍,該依賴範圍不會對三種classpath產生實際影響。
傳遞性依賴與依賴範圍
* 當第二直接依賴範圍爲compile的時候,傳遞性依賴的範圍與第一直接依賴範圍一致
* 當第二直接依賴範圍爲test的時候,依賴不會傳遞
* 當第二直接依賴範圍爲provided的時候,只傳遞第一直接依賴範圍爲provided的依賴,且傳遞性依賴的範圍也是provided
* 當第二直接依賴範圍爲runtime的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致,但compile例外,此時傳遞依賴的範圍爲runtime。
依賴調解
解決傳遞性依賴時,引入同一包的不同版本的問題。
這種情況下maven選擇依賴的原則是1)路徑最近這優先,2)路徑層數相同的情況下,聲明在前者優先。
依賴最佳實踐
- 排除依賴,使用元素,下只需要groupId和artifactId,不需要version。因爲maven解析後的依賴中不會存在版本不同的兩個包。
- 歸類依賴,對於相互關聯的同一組依賴,比如springframework,用maven屬性來管理它們的version字段,可以保證依賴版本的統一。使用元素定義屬性,使用${property-name}引用屬性。
- 優化依賴
- mvn dependency:list可以查看當前的所有依賴。
- mvn dependency:tree可以以樹形結構查看。
- mvn dependency:analyze可以分析依賴中存在的問題。可以發現用到未顯示聲明的依賴,也可以發現聲明但未顯示用到的依賴,但是僅做靜態分析,對於運行時依賴無法發現。
倉庫
中央倉庫,定義在超級pom中,位於
在pom中配置遠程倉庫
- 可以使用的子元素來在項目中定義遠程倉庫,每個元素定義一個遠程倉庫,每個repository的id必須唯一。
- releases元素用來控制發佈版構件的下載,true表示下載發佈版本的構件。
- snapshots元素用來控制快照版構件的下載,false表示不會下載快照版本的構件。
- layout元素的值爲default表示採用maven2和maven3的佈局,而不是maven1的佈局legacy。
- releases和snapshots還有兩個子元素updatePolicy和checksumPolicy
- updatePolicy用來配置Maven從遠程倉庫的更新頻率,默認值是daily,可用值還有never、always、interval:X(分鐘)。
- checksumPolicy用來配置Maven檢查文件校驗和的策略。當構件被部署到Maven倉庫時會同時不是對應的校驗和文件,如果校驗和驗證失敗,且checksumPolicy爲默認值warn,則輸出警告信息,可用還包括fail、ignore。
<project>
...
<repositories>
<repository>
<id>jboss</id>
<name>JBoss reposiotry</name>
<url>http://repository.jboss.com/maven2/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
...
</project>
遠程倉庫認證
認證信息必須配置在settings.xml文件中,不能像倉庫一樣配置在pom中。這樣比較安全,因爲settings是在本機的,pom是在代碼庫中的。
倉庫信息認證配置舉例,配置中的id要與倉庫的id保持一致
<settings>
<servers>
<server>
<id>repository id here</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
</settings>
部署至遠程倉庫
可以在pom中進行配置,將項目生成的構件部署至指定的遠程倉庫,這裏利用的是pom中的子元素
<project>
<distributionManagement>
<repository>
<id>proj-releases</id>
<name>project release repository</name>
<url>http://x.x.x.x/maven2/repositories/proj-releases</url>
</repository>
<snapshotRepository>
<id>proj-snapshots</id>
<name>project snapshot repository</name>
<url>http://x.x.x.x/maven2/repositories/proj-snapshots</url>
</snapshotRepository>
</distributionManagement>
</project>
在pom中配置好要部署到的遠程倉庫後,執行maven clean deploy就可以將輸出的構件部署到對應的遠程倉庫了。
快照版本
快照版本是爲了協同開發時,需要頻繁從遠程倉庫下載最新構件而又不希望更新版本號而發明的手段。快照版本會由maven自動爲構件打上時間戳,這樣就能永遠獲取最新的構件。快照版本只應該在組織內部的項目或者模塊間依賴使用。
從倉庫解析依賴的機制
- 當本地倉庫沒有依賴的構架的時候,Maven會自動從遠程倉庫下載,當依賴版本爲快照時,maven會自動找到最新的快照。過程如下:
- 當依賴範圍爲system時,maven直接從本地文件系統解析構件
- 根據依賴座標計算構件在倉庫中的路徑後,嘗試從本地倉庫尋找構件,如果有,則解析成功
- 如果本地沒有,如果依賴的是發佈版構件,則遍歷所有遠程倉庫,發現後下載使用
- 如果依賴版本是RELEASE或LATEST,則基於更新策略讀取所有倉庫的元數據groupId/artifactId/maven-metadata.xml將其與本地倉庫的對應元數據合併後,計算出RELEASE或者LATEST的真實值,然後基於真實值去下載構件。
- 如果依賴的版本是SNAPSHOT,則基於更新策略去讀取所有遠程倉庫的元數據groupId/artifactId/version/maven-metadata.xml,將其與本地倉庫的對應元數據合併後,得到最新快照版本的值,然後根據真實值檢查本地倉庫或下載。
- 如果最後解析得到的是時間戳格式的快照,則複製其時間戳格式的文件至非時間戳格式,如SNAPSHOT,並使用該非時間戳格式的文件。
- 建議不要在依賴聲明中使用RELEASE、LATEST等不明晰的聲明。
鏡像
鏡像和私服不同,鏡像是原始倉庫的完全拷貝,能夠連接遠程倉庫的私服則像是個帶有緩存的代理。配置了鏡像後會完全屏蔽對被鏡像庫的訪問,因此鏡像庫的穩定性很重要。
鏡像配置舉例,在settings.xml中配置
<settings>
<mirrors>
<mirror>
<id>nexus-osc</id>
<mirrorOf>central</mirrorOf>
<name>Nexus osc</name>
<url>http://maven.oschina.net/content/groups/public/</url>
</mirror>
</mirrors>
</settings>
其中元素配置被鏡像庫的id,可以使用通配符。匹配所有倉庫,external:匹配所有遠程倉庫,repo1,repo2可以指定多個倉庫,*,!repo1,匹配所有遠程倉庫,repo1除外,使用!將倉庫從匹配中排除。使用通配符的mirrorOf可以配合私服使用,在私服上配置所有遠程庫,本地只需要配置鏡像爲私服即可訪問多個遠程庫。
生命週期和插件
maven的生命週期對構件過程進行抽象,主要包含項目的清理、初始化、編譯、測試、打包、集成測試、驗證、部署和站點生成等構建步驟。生命週期抽象了構建的各個步驟,定義了它們的次序,但是沒有提供具體的實現,具體的實現通過插件機制來完成。每個構建步驟可以綁定一個或者多個插件行爲。
maven日常使用中,命令行的輸入往往對應了生命週期,maven的生命週期是抽象的,生命週期本身並不做任何實際工作,實際行爲都由插件來完成。maven爲大部分生命週期綁定了默認插件,用戶可以配置插件的行爲來控制構建過程。
三套生命週期
maven有三套生命週期,分別是clean、default和site。每個生命週期都包含一些有順序的階段,後面的階段依賴於前面的階段,用戶和maven最直接的交互方式就是調用這些生命週期的階段。各套生命週期之間是項目獨立的,而一個生命週期的階段是有前後依賴關係的。
* clean生命週期,目的是項目清理,包含的階段有pre-clean、clean和post-clean三個階段。
* pre-clean,執行一些清理前需要完成的工作
* clean,清理上一次構建生成的文件
* post-clean,執行一些清理後需要完成的工作
* default生命週期,目的是構建項目,定義了構建時所需要執行的所有步驟,包含如下階段
* validate,校驗項目是否正確以及所有必須的信息是否齊備
* initialize
* generate-sources
* process-sources
* generate-resources
* process-resources,處理項目主資源文件,一般來說是對src/main/resources目錄的內容進行變量替換等工作,然後複製到項目輸出的主classpath目錄中
* compile,編譯項目的主源碼,一般來說是編譯src/main/java目錄下的java文件至項目輸出的主classpath目錄下。
* process-classes
* generate-test-sources
* process-test-sources
* generate-test-resources
* process-test-resources
* test-compile,編譯項目的測試代碼
* process-test-classes
* test,使用單元測試框架進行測試,測試代碼不會被打包或部署
* prepare-package
* package,接受編譯好的代碼,打包成可發佈的格式,比如JAR、WAR
* pre-integration-test
* integration-test
* post-integration-test
* verify,運行並檢查集成測試結果來保證質量
* install,將包安裝到Maven本地倉庫,供本地其它maven項目使用
* deploy,將最終的包複製到遠程倉庫,供其他開發人員和Maven項目使用。
* site生命週期,目的是建立和發佈項目站點,maven能夠基於POM所包含的信息,自動生成一個項目站點,方便團隊交流和發佈項目信息,包括以下階段
* pre-site,執行一些在生成項目站點之前需要完成的工作
* site,生成項目站點文檔
* post-site,執行一些在生成項目站點之後需要完成的工作
* site-deploy,將生成的項目站點發布到服務器上
Maven各個生命週期的詳細描述參見:官方文檔
從命令行執行maven是,主要就是調用的生命週期的階段,由於各個生命週期階段依賴於前面的階段,因此執行時會將該階段前面的階段也一併順序執行。
插件目標和插件半丁
直觀上來講,插件目標就是mvn dependency:tree,這裏的tree就是插件目標(Plugin Goal)。每個插件目標對應一個功能,冒號前面是插件前綴,冒號後面是插件目標。
maven的生命週期和插件相互綁定,具體來說是生命週期的階段與插件目標綁定。
內置綁定
爲了實現零配置使用,maven在覈心爲一些主要的生命週期階段綁定了很多插件的目標,當用戶通過命令行調用生命週期階段時,對應的插件目標就會執行相應的任務。
默認生命週期階段與插件目標的綁定關係如下表:
生命週期階段 | 插件目標 |
---|---|
pre-clean | |
clean | maven-clean-plugin:clean |
post-clean | |
pre-site | |
site | maven-site-plugin: site |
post-site | |
site-deploy | maven-site-plugin:deploy |
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 |
install | maven-install-plugin:install |
deploy | maven-deploy-plugin:deploy |
自定義綁定
可以在插件中配置phase將插件目標綁定到某個特定的生命週期階段。
代碼示例如下,如下將maven-source-plugin:jar-no-fork目標綁定到default生命週期的verify階段
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins<groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
一般插件編寫時也會定義默認綁定階段,可以通過mvn-help-plugin查看插件詳細信息,瞭解插件目標的默認綁定階段。
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:3.3 -Ddetail
當多個插件目標綁定到同一個階段時,這些插件聲明的先後順序決定了目標的執行順序。
插件配置
除了配置插件目標綁定外,還可以對目標的參數進行一些配置。用戶可以通過命令行和POM來配置這些參數。
可以在插件配置中定義插件任務,是插件配置僅應用到任務,而不是全局。
獲取插件信息
基本上所有的插件都來自Apache和Codehaus
插件信息可以訪問插件站點來獲取,也可以使用maven-help-plugin插件來獲取幫助。
插件解析機制
爲了方便用戶使用和配置插件,maven不需要用戶提供插件的完整座標就可以解析得到正確的插件,使用插件前綴可以簡單的引用插件。
與普通構件一樣插件構件也基於座標存儲在maven倉庫中。插件的遠程倉庫配置與普通構件的遠程倉庫配置不同,插件的遠程倉庫使用元素來定義,在配置插件時,如果是官方插件可以省略groupId定義,不推薦這樣使用。當沒有配置版本時,會拉取所有遠程插件倉庫和本地倉庫的元數據庫中獲取最新版本,這裏maven 3不會獲取快照版本。
用戶可以使用插件前綴來簡潔的訪問插件,這裏插件前綴與groupId:artifactId一一對應,對應關係存儲在倉庫元數據庫中,位於groupId/maven-metadata.xml中。這裏的groupId默認指的是org.apache.maven.plugins和org.codehaus.mojo這兩個,也可以通過配置settings.xml,通過元素配置自定義的插件掃描的groupId。
在上述的maven-metadata.xml中存在元素描述插件的前綴。
聚合和繼承
聚合
可以用一個項目聚合其它項目,實現一起編譯打包。聚合項目的packaging值爲pom,使用元素來包含各個子模塊。這裏每個module的值都是一個當前pom的相對目錄。一般爲了快速定位內容,模塊所處的目標名稱應該與其artifactId一致。爲了方便用戶構建項目,一般將聚合模塊放在項目目錄的最頂層,其它模塊作爲聚合目錄的子目錄存在。
繼承
pom可以繼承,作爲父模塊的pom,其packaging類型必須爲pom。父模塊的目的是爲了消除重複的配置,它本身不包含除POM之外的項目文件,也不需要src/main/java之類的文件夾。可以在子模塊中使用元素來聲明父模塊,子元素聲明父pom的位置,默認是../pom.xml。
可以繼承的pom元素包括:
* groupId和version
* description,項目描述信息
* organizition
* inceptionYear, 項目的創始年份
* url,項目的URL地址
* developers,項目的開發者信息
* contributors,項目的貢獻者信息
* distributionManagement,項目的部署配置
* issueManagement,項目的缺陷跟蹤系統信息
* ciManagement,項目的持續集成系統信息
* scm,項目的版本控制系統信息
* mailingLists,項目的郵件列表信息
* properties,自定義的maven屬性
* dependencies,項目的依賴
* dependencyManagement,項目的依賴管理配置
* repositories,項目的倉庫配置
* build,包括項目的源碼配置,輸出目錄配置,插件配置,插件管理配置等
* reporting,包括項目的報告輸出配置,報告插件配置等。
依賴繼承管理
maven提供的dependencyManagement元素能夠讓子模塊集成到父模塊的依賴配置,還能保證子模塊依賴使用的靈活性。在dependencyManagement下聲明的依賴不會引入實際的依賴,但它能夠約束dependencies下的依賴使用。可以簡化子模塊dependencies的編寫,在父模塊中通過元素聲明過的依賴,在子模塊中只需要指明groupId和artifactId即可,其它配置可以從父模塊繼承。還可以配置import依賴範圍使用,指的就是將指定pom的配置導入。import依賴範圍一般指向packaging值爲pom的模塊。
除了元素用來管理普通依賴外,maven還提供了元素來管理插件。該元素配置的插件依賴不會導致實際的插件調用行爲,只有pom中配置了真正的元素並且groupId和artifactId與中聲明的一致時,的配置纔會影響插件的行爲。
約定優於配置
原因:使用約定可以大量減少配置,大量的約定配置從超級pom來繼承,比如源碼的位置,輸出目錄,默認打包方式等。
超級pom
maven 3的超級pom位置是$M2_HOME/lib/maven-model-builder-x.x.x.jar中的org.apache.maven.model.pomx-4.0.0.xml。
超級pom的內容包括:
* 中央倉庫的定義
* 中央插件倉庫的定義
* 項目結構的定義,由元素定義,包括
* 項目的主輸出目錄,由子元素定義
* 項目主代碼輸出目錄,由子元素定義
* 最終構件的名稱格式,由子元素定義
* 測試代碼輸出目錄,由子元素定義
* 主源碼目錄,由子元素定義
* 腳本源碼目錄,由子元素定義
* 測試源碼目錄,由子元素定義
* 主資源目錄,由子元素的子元素的子元素定義
* 測試資源目錄,由子元素的子元素的子元素定義
* 插件版本定義,由pluginManagement元素定義
* 項目報告輸出目錄的配置,項目發佈profile等。
反應堆
就是多模塊聚合在一起的構建項目,需要關注的是各個模塊的構建順序。
構建順序的計算規則如下,maven按照module聲明的順序讀取pom,如果該pom沒有依賴模塊,那麼就構建該模塊,否則先構建其依賴模塊,如果該依賴模塊還依賴於其它模塊,則進一步構建其它依賴模塊。模塊間的依賴關係構成一個有向非循環圖,依賴之間不允許出現循環,否則會報錯。