依賴機制是Maven最爲用戶熟知的特性之一,同時也是Maven所擅長的領域之一。單個項目的依賴管理並不難,但是當你面對包含數百個模塊的多模塊項目和應用時,Maven能幫你保證項目的高度控制力和穩定性。
一.傳遞性依賴
傳遞性依賴是Maven2.0的新特性。假設你的項目依賴於一個庫,而這個庫又依賴於其他庫。你不必自己去找出所有這些依賴,你只需要加上你直接依賴的庫,Maven會隱式的把這些庫間接依賴的庫也加入到你的項目中。這個特性是靠解析從遠程倉庫中獲取的依賴庫的項目文件實現的。一般的,這些項目的所有依賴都會加入到項目中,或者從父項目繼承,或者通過傳遞性依賴。
傳遞性依賴的嵌套深度沒有任何限制,只是在出現循環依賴時會報錯。
傳遞性依賴會導致包含庫的依賴圖增長的非常大。爲了解決這個問題,Maven也提供了額外的機制,能讓你指定哪些依賴會被包含:
-
依賴調解 – 當項目中出現多個版本構件依賴的情形,依賴調解決定最終應該使用哪個版本。目前,Maven 2.0只支持“短路徑優先”原則,意思是項目會選擇依賴關係樹中路徑最短的版本作爲依賴。當然,你也可以在項目POM文件中顯式指定使用哪個版本。值得注意的是,在Maven2.0.8及之前的版本中,當兩個版本的依賴路徑長度一致時,哪個依賴會被使用是不確定的。不過從Maven 2.0.9開始,POM中依賴聲明的順序決定了哪個版本會被使用,也叫作”第一聲明原則”。
- “短路徑優先”意味着項目依賴關係樹中路徑最短的版本會被使用。例如,假設A、B、C之間的依賴關係是A->B->C->D(2.0)和A->E->(D1.0),那麼D(1.0)會被使用,因爲A通過E到D的路徑更短。但如果你想要強制使用D(2.0),那你也可以在A中顯式聲明對D(2.0)的依賴。
-
依賴管理 – 在出現傳遞性依賴或者沒有指定版本時,項目作者可以通過依賴管理直接指定模塊版本。之前的章節說過,由於傳遞性依賴,儘管某個依賴沒有被A直接指定,但也會被引入。相反的,A也可以將D加入元素中,並在D可能被引用時決定D的版本號。
-
依賴範圍 – 你可以指定只在當前編譯範圍內包含合適的依賴。 下面會介紹更多相關的細節。
-
排除依賴 – 如果項目X依賴於項目Y,項目Y又依賴項目Z,項目X的所有者可以使用”exclusion”元素來顯式排除項目Z。
-
可選依賴 – 如果項目Y依賴項目Z,項目Y的所有者可以使用”optional”元素來指定項目Z作爲X的可選依賴。那麼當項目X依賴項目Y時,X只依賴Y並不依賴Y的可選依賴Z。項目X的所有者也可以根據自己的意願顯式指定X對Z的依賴。(你可以把可選依賴理解爲默認排除)。
二.依賴範圍
依賴範圍會影響傳遞性依賴,同時也會影響項目構建任務中使用的classpath。
Maven有以下6種依賴範圍:
-
compile:這是默認範圍。如果沒有指定,就會使用該依賴範圍。編譯依賴對項目所有的classpath都可用。此外,編譯依賴會傳遞到依賴的項目。
-
provided:和compile範圍很類似,但provided範圍表明你希望由JDK或者某個容器提供運行時依賴。例如,當使用Java EE構建一個web應用時,你會設置對Servlet API和相關的Java EE APIs的依賴範圍爲provided,因爲web容器提供了運行時的依賴。provided依賴只對編譯和測試classpath有效,並且不能傳遞。
-
runtime:runtime範圍表明編譯時不需要依賴,而只在運行時依賴。此依賴範圍對運行和測試classpath有效,對編譯classpath無效。
-
test:test範圍表明使用此依賴範圍的依賴,只在編譯測試代碼和運行測試的時候需要,應用的正常運行不需要此類依賴。
-
system:系統範圍與provided類似,不過你必須顯式指定一個本地系統路徑的JAR,此類依賴應該一直有效,Maven也不會去倉庫中尋找它。
-
import(Maven2.0.9及以上):import範圍只適用於pom文件中的
<dependencyManagement>
部分。表明指定的POM必須使用<dependencyManagement>
部分的依賴。因爲依賴已經被替換,所以使用import範圍的依賴並不影響依賴傳遞。
每類依賴範圍(除了import)通過不同方式影響傳遞性依賴,具體如下表所示。最左側一列代表了直接依賴範圍,最頂層一行代表了傳遞性依賴的範圍,行與列的交叉單元格就表示最終的傳遞性依賴範圍。表中的“-“表示該傳遞性依賴將會被忽略。
compile | provided | runtime | test | |
compile | compile(*) | – | runtime | – |
provided | provided | – | provided | – |
runtime | runtime | – | runtime | – |
test | test | – | test | – |
(*)注意:這裏本來應該是compile範圍,那樣的話compile範圍都必須顯式指定-然而,有這樣一種情況,你依賴的、繼承自其它庫中的類的庫必須在編譯時可用。考慮到這個原因,即使在依賴性傳遞情況下,編譯時依賴仍然是compile範圍。
三.依賴管理
集中管理
Maven提供了一個機制來集中管理依賴信息,叫做依賴管理元素””。假設你有許多項目繼承自同一個公有的父項目,那可以把所有依賴信息放在一個公共的POM文件,並且在子POM中簡單第引用該構件即可。通過一些例子可以更好的解釋這個機制。下面是兩個繼承自同一個父項目的POM:
項目A
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
項目B
<project>
...
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
這兩個POM都依賴於同一個模塊,同時每個POM又各自依賴於一個無關的模塊。父項目的POM詳細信息如下所示:
<project>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>group-c</groupId>
<artifactId>excluded-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<version>1.0</version>
<type>bar</type>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
這樣兩個子項目的POM文件就簡單多了。
<project>
...
<dependencies>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-a</artifactId>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<!-- This is not a jar dependency, so we must specify type. -->
<type>bar</type>
</dependency>
</dependencies>
</project>
<dependencies>
<dependency>
<groupId>group-c</groupId>
<artifactId>artifact-b</artifactId>
<!– This is not a jar dependency, so we must specify type. –>
<type>war</type>
</dependency>
<dependency>
<groupId>group-a</groupId>
<artifactId>artifact-b</artifactId>
<!– This is not a jar dependency, so we must specify type. –>
<type>bar</type>
</dependency>
</dependencies>
注意:在這兩個POM文件的依賴中,我們必須指定<type/>
元素。因爲與依賴管理元素匹配的依賴引用最小信息集是{groupId, artifactId, type, classfier}。許多情況下,依賴指向的jar不需要指定classfier。因爲默認type是jar,默認classfiler爲空,所以我們可以把信息集設置爲{groupId, artifactId}。
控制傳遞性依賴
依賴管理元素第二個非常有用的功能是控制傳遞性依賴中構件的版本。例子如下
項目A:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>A</artifactId>
<packaging>pom</packaging>
<name>A</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>c</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>d</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
項目B:
<project>
<parent>
<artifactId>A</artifactId>
<groupId>maven</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>B</artifactId>
<packaging>pom</packaging>
<name>B</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>d</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>c</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
在B中,構件a,b,c和d的版本都是1.0。
-
a和c都被聲明爲這個項目的依賴,根據依賴調解,a和c的版本都是1.0.同時a和c的依賴範圍都被顯式指定爲runtime。
-
b定義在B的父項目的
<dependencyManagement>
元素中,因爲在依賴性傳遞中<dependencyManagement>
優先於依賴調解,所以b的版本是1.0,b是編譯依賴範圍。 -
最後,d是定義在B的
<dependencyManagement>
元素中。
依賴管理的標籤詳細描述信息可以從這裏獲取項目描述符引用
導入依賴
這個章節描述的特性只在Maven2.0.9及之後的版本纔有。這意味着更早版本的Maven不會解析包含import元素的pom文件。因此在使用該特性前,你必須慎重考慮。如果你打算使用這個特性,我們建議你使用enforcer插件來強制使用Maven2.0.9及以上版本。
前面的例子描述了怎麼通過繼承來指定管理的依賴。然而,這對於更大的項目通常會更復雜,因爲一個項目只能繼承自一個父項目。爲了解決這個問題,項目可以導入其他項目的管理依賴,這可以通過聲明依賴一個包含值爲import
的<scope>
元素的構件來實現。
項目B:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>B</artifactId>
<packaging>pom</packaging>
<name>B</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>maven</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>d</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>c</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
假設A就是上一個例子中定義的POM,那麼最終的結果也是一致的。除了在B中定義的d模塊,所有A的管理依賴都會導入到B中。
項目X:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>X</artifactId>
<packaging>pom</packaging>
<name>X</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>b</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
項目Y:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>Y</artifactId>
<packaging>pom</packaging>
<name>Y</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>test</groupId>
<artifactId>a</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>c</artifactId>
<version>1.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
項目Z:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>maven</groupId>
<artifactId>Z</artifactId>
<packaging>pom</packaging>
<name>Z</name>
<version>1.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>maven</groupId>
<artifactId>X</artifactId>
<version>1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>maven</groupId>
<artifactId>Y</artifactId>
<version>1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
在上面的例子中,Z導入了X和Y的管理依賴。不過有個問題,X和Y都包含了依賴a。在這裏,會使用1.1版本的a,因爲X先被聲明,並且a沒有在Z的依賴管理中聲明。
這個過程是遞歸進行的。假如X導入了另外的POM,Q,那麼當解析Z的時候,所有Q的管理依賴看上去就都像在X中定義的一樣。
當定義一個用於構建多項目的包含一些相關構件的依賴“庫”時,導入依賴就十分有效。從“庫”中引用一個或多個構件到項目中,是一種很常見的做法。然而,
保持項目中使用的依賴版本與庫中發佈的版本一致會有點麻煩。下面的模式描述了怎麼生成一個供其它項目使用的“物料清單”(BOM)。
項目的根元素是BOM pom文件。它定義了庫中創建的所有構件版本。其它要使用該庫的項目必須將該pom導入到其pom文件中的<dependencyManagement>
元素中。
<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.test</groupId>
<artifactId>bom</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<project1Version>1.0.0</project1Version>
<project2Version>1.0.0</project2Version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>project1</artifactId>
<version>${project1Version}</version>
</dependency>
<dependency>
<groupId>com.test</groupId>
<artifactId>project2</artifactId>
<version>${project1Version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>parent</module>
</modules>
</project>
parent子項目將BOM pom作爲它的父項目。這是一個簡單的多項目pom。
<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>
<parent>
<groupId>com.test</groupId>
<version>1.0.0</version>
<artifactId>bom</artifactId>
</parent>
<groupId>com.test</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>project1</module>
<module>project2</module>
</modules>
</project>
接下來是真正的pom文件。
<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>
<parent>
<groupId>com.test</groupId>
<version>1.0.0</version>
<artifactId>parent</artifactId>
</parent>
<groupId>com.test</groupId>
<artifactId>project1</artifactId>
<version>${project1Version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>
</project>
<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>
<parent>
<groupId>com.test</groupId>
<version>1.0.0</version>
<artifactId>parent</artifactId>
</parent>
<groupId>com.test</groupId>
<artifactId>project2</artifactId>
<version>${project2Version}</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
</dependencies>
</project>
下面的例子說明了怎麼在項目中使用“庫”,而不必指定依賴模塊的版本。
<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.test</groupId>
<artifactId>use</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>project1</artifactId>
</dependency>
<dependency>
<groupId>com.test</groupId>
<artifactId>project2</artifactId>
</dependency>
</dependencies>
</project>
最後,當創建引入依賴的項目時,需要注意以下幾點:
-
不要嘗試引入在當前pom中定義的子模塊pom。那會導致不能定位pom和編譯失敗。
-
絕不要聲明導入其他pom作爲目標pom的父項目(或者祖父項目等)的pom文件。這會導致循環解析,並觸發異常。
當 -
引用有傳遞性依賴的模塊時,需要指定依賴模塊的版本。不這樣做,這些模塊可能沒有確定的版本,從而導致編譯失敗。(這在任何情況下都應該是一個最佳實踐,因爲它保證了模塊版本的不變性)
四.系統依賴
系統範圍的依賴應該是一直可用,並且Maven不會去倉庫中查找。系統範圍依賴通常是指JDK或者VM提供的依賴。所以,系統依賴適用於這種情況:以前可以單獨獲取,但現在是由JDK提供的依賴。典型的例子就是JDBC標準擴展或者Java認證和授權服務(JAAS)。一個簡單的例子如下:
<project>
...
<dependencies>
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
</dependencies>
...
</project>
如果你的構件依賴於JDK的tools.jar,那麼系統路徑的值如下所示:
<project>
...
<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
...
</project>
轉載自併發編程網 – ifeve.com本文鏈接地址: 《Maven官方文檔》-Maven依賴機制簡介