1.1何為maven坐標
Mavne的一大功能是管理項目依賴,為了能自動化的解析任何一個Java構件,maven就必須將它們唯一標識,這就依賴管理的底層基礎—-坐標。
Maven的世界中擁有數量非常巨大的構件,也就是平時用的一些jar、war等文件,在Maven為這些構件引入坐標概念之前,我們無法使用任何一種方式來唯一標識所有這些構件。因此maven定義了這樣組規則:世界上任何一個構件都可以使用Maven坐標唯一標識,maven坐標元素包括groupId、artifactId、version、packaging、classifier?,F在,只要我們提供正確的坐標元素,maven就能找到對應的組件。比如說,當需要使用Java5平臺上TestNG的5.8版本時,就告訴Maven:“groupId=org.testng;artifactId=testng;version=5.9;classifier=jdk15
”,maven就會從倉庫中尋找相應的構件供我們使用。Maven是從哪里下載構件的呢?maven內置了一個中央倉庫的地址(http://repol.maven.org/maven2),該中央倉庫包含了世界上大部分流行的開源項目組件,maven會在需要的時候去那里下載。
1.2坐標詳解
先看一組坐標定義,如下:
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
這是nexus-indexer的坐標定義,nexus-indexer是一個對maven倉庫編纂索引并提供搜索功能的類庫,它是Nexus項目的一個子模塊。下面解釋一下各個坐標元素:
- groupId:定義當前maven項目隸屬的實際目錄。首先,maven項目和實際項目不一定是一對一的關系。其次,groupId不應該對應項目隸屬的組織或公司。最后,groupId的表示方式與Java包名的表示方式類似,通常與域名反向一一對應。
- artifactId:該元素定義實際項目中的一個Maven項目(模塊),推薦的做法是使用實際項目名稱作為前綴。
- version:該元素定義Maven項目當前所處版本。
- packaging:該元素定義Maven項目的打包方式。首先。打包方式通常與所生成構件的文件擴展名對應。其次,打包方式會影響到構件的生命周期,比如jar打包和war打包會使用不同的命令。最后,當不定義packaging的時候,Maven會使用默認值jar。
- classifier:該元素用來幫助定義構建輸出的一些附屬構件。附屬構件與主構件對應,如上例中的主構件是nexus-indexer-2.0.0.jar,該項目可能會通過使用一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar這樣一些附屬構件。
上述五個元素中,groupId、artifactId、version是必須定義的,packaging是可選的(默認為jar),而classifier是不能直接定義的。
理解清楚Maven坐標之后,我們就能開始討論Maven的依賴管理了。
1.3依賴的配置
一個依賴聲明可以包含如下的一些元素:
<project>
…
<dependencies>
<dependency>
<groupId>…</groupId>
<artifactId>…</artifactId>
<version>…</version>
<type>…</type>
<scope>…</scope>
<optional>…</optional>
<exclusions>
<exclusion>
…
</exclusion>
…
</exclusions>
</dependency>
…
</dependencies>
…
</project>
Dependencies可以包含一個或者多個dependency元素,以聲明一個或者多個項目依賴。每個依賴可以包含的元素有:
- groupId、artifactId和version:依賴的基本坐標,對于任何一個依賴來說,基本坐標是最重要的,Maven根據坐標才能找到需要的依賴。
- type:依賴的類型,對應于項目坐標定義的packaging。大部分情況下,該元素不必聲明,其默認值為jar。
- scope:依賴的范圍。
- optional:標記依賴是否可選。
- exclusions:用來排除傳遞性依賴。
1.4依賴范圍
首先需要知道,Maven在編譯項目主代碼的時候需要使用一套classpath。其次,Maven在編譯和執行測試的時候會使用另外一套classpath。最后,實際運行Maven項目的時候,又會使用一套classpath。
依賴范圍就是用來控制依賴與這三種classpath(編譯classpaht、測試classpath、運行classpath)的關系,Maven有以下幾種依賴范圍:
- compile:編譯依賴范圍。如果沒有指定,就會默認使用該依賴范圍。使用此依賴范圍的Maven依賴,對于編譯、測試、運行三種classpath都有效。
- test:測試依賴范圍。使用此范圍依賴的Maven依賴,只對測試classpath有效,在編譯主代碼或者運行項目的時候將無法使用此類依賴
- provided:已提供依賴范圍。使用此依賴范圍的Maven依賴,對于編譯和測試classpath都有效,但是在運行時無效
- runtime:運行時依賴范圍。使用此依賴范圍的Maven依賴,對于測試和運行classpath有效,但在編譯主代碼時無效。
- system:系統依賴范圍。該依賴與三種classpath的關系,和provided依賴范圍完全一致。但是,使用system范圍的依賴時必須通過systemPath元素顯式地指定依賴文件路徑。由于此類依賴不是通過Maven倉庫解析的,而且往往與本機系統綁定,可能造成構件的不可移植,因此應該謹慎使用。SystemPath元素可以引用環境變量,如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/tr.jar</systemPath>
</dependency>
- import:導入依賴范圍。該依賴范圍不會對三種classpath產生實際的影響。
1.5傳遞性依賴
何為傳遞性依賴?現在舉一個例子:項目中有一個compile范圍的spring-core依賴,spring-core有一個compile范圍的commons-logging依賴,那么commons-logging就會成為該項目的compile范圍依賴,commons-logging是該項目的一個傳遞依賴。
Maven會解析各個直接依賴的POM,將那些必要的間接依賴以傳遞性依賴的形式引入到當前項目中。
依賴范圍不僅可以控制依賴與三種classpath的關系,還對傳遞性依賴產生影響。假設A依賴于B,B依賴于C,我們說A對于B是第一直接依賴,B對于C是第二直接依賴,A對于C是傳遞性依賴。第一直接依賴的范圍和第二直接依賴的范圍決定了傳遞性依賴的范圍,如表,最左邊一列表示第一直接依賴范圍,最上面一行表示第二直接依賴范圍,中間的交叉單元格則表示傳遞性依賴范圍
compile |
test |
provided |
runtime | |
compile |
compile |
—— |
—— |
runtime |
test |
test |
—— |
—— |
test |
provided |
provided |
—— |
provided |
provided |
runtime |
runtime |
—— |
—— |
runtime |
仔細觀察一下表可以發現這樣的規律:當第二直接依賴的范圍是compile的時候,傳遞性依賴的范圍與第一直接依賴的范圍一致;當第二直接依賴的范圍是test的時候,依賴不會得以傳遞;當第二直接依賴的范圍為provided的時候,只傳遞第一直接依賴的范圍為provided的依賴,且傳遞性依賴的范圍同樣為provided;當第二直接依賴的范圍是runtime的時候,傳遞性依賴的范圍與第一直接依賴的范圍一致,但compile除外,此時傳遞性依賴的范圍為runtime。
1.6依賴調解
Maven引入的傳遞性依賴機制,一方面大大簡化和方便了依賴聲明,另一方面,大部分情況下我們只需要關心項目的直接依賴是什么,而不用考慮這些直接依賴會引入什么傳遞依賴。但是有時候,當傳遞性依賴造成問題的時候,我們就需要清楚的知道該傳遞性依賴是從哪條依賴路徑引入的。
例如A->B->C->X(1.0)、A->D->X(2.0),X是A的傳遞性依賴,但是兩條依賴路徑上有兩個版本的X,那個X會被Maven解析使用呢?Maven依賴調解的第一原則是:路徑最近者優先。依賴調解第一原則不能解決所有問題,比如:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依賴路徑長度是一樣的,到底誰會解析?在Maven2.0.9開始,Maven定義了依賴調解的第二原則:第一聲明者優先。