Maven學習筆記六【依賴機制】
本節來學習Maven地依賴機制。
依賴關係管理是Maven的一個核心特性。管理單個專案的依賴關係很容易。管理由數百個模組組成的專案的依賴關係是可能的。Maven在定義、建立和維護具有良好定義地類路徑和庫版本的可重複構建方面幫助很大。
傳遞依賴
Maven通過自動包含傳遞依賴項,來避免查詢和指定自己的依賴項所需要的庫。
通過從指定的遠端儲存庫中讀取依賴項的專案檔案,可以簡化該特性。通常,這些專案的所有依賴項都在專案中使用,專案從其父專案繼承的依賴項或從其依賴項繼承的依賴項也是如此。g
可以從依賴項收集的幾部數量沒有限制,只有在發現迴圈依賴關係時才會出現問題。
通過傳遞依賴關係,所包含庫地圖可以快速增長到相當大。由於這個原因,還有一些附加特性限制了所包含的依賴關係:
-
Dependency mediation
- 這決定了當依賴項遇到多個版本時,將選擇哪個版本。Maven選擇“最近的定義”。也就是說,它使用依賴關係樹中與專案最接近地依賴關係版本。我們可以通過在專案的POM重顯式地宣告版本來保證它。請注意,如果兩個依賴版本在依賴樹中的深度相同,則第一個宣告獲勝。
- “最近的定義” 意味著所使用的版本將是依賴關係樹中與專案最接近的版本。例如,如果A、B和C地依賴關係定義為A -> B -> C -> D2.0和A -> E -> D1.0。那麼,在構建A時將使用D1.0,因為從A到D,第二條路徑更短。可以顯式地在A中新增對D2.0的依賴,以強制使用D2.0。
- Dependency management - 這允許專案作者直接指定在傳遞依賴項中或在沒有指定版本的依賴項中藥使用的版本。在上面的示例中,依賴項直接新增到A中,即使A沒有直接使用它。相反,A可以在dependencyManagement部分中包含D作為依賴項,並直接控制在引用D時將使用哪個版本。
- Dependency scope - 這允許只包含適合於構建當前階段的依賴項,下面會詳細講到。
- Excluded dependencies 如果專案X依賴於專案Y,而專案Y依賴於專案Z,專案X的所有者可以使用exclusion標籤顯式地將專案Z排除。
- Optional dependencies - 如果專案Y依賴於專案Z,專案Y的作者可以使用optional元素將專案Z標記為可選依賴項。當專案X依賴於專案Y時,X只依賴於Y,而不依賴於Y的可選依賴項Z。(將可選依賴項視為預設排除可能會有助於理解)
雖然可傳遞依賴項可以幫助我們包含隱含的所需依賴項,但是最好是明確指定自己的原始碼中所使用的依賴專案。當依賴的專案改變了它本身的依賴關係時,這個最佳實踐證明了其價值。
比如專案A依賴了專案B,並且專案B依賴了專案C。如果直接使用專案C中的元件,而不是在專案A中指定專案C,則當專案B突然更新/刪除專案C的依賴時,它可能會導致生成失敗。
直接指定依賴關係的另一個原因是,它為我們的專案提供了更好的文件:可以通過閱讀專案中的POM檔案瞭解更多資訊。
Maven還提供了dependency:analyze
外掛來分析這些依賴關係:它有助於使這種最佳實踐更容易實現。
Dependency Scope
依賴範圍用於限制依賴的傳遞性,並且還用於影響用於各種構建任務的類路徑。
有6種scope可用:
-
compile
如果沒有指定的話,這是預設作用域。編譯依賴項再專案的所有類路徑中都可用。此外,這些依賴關係被傳播到依賴的專案。
-
provided
這非常類似於編譯,但是表示希望JDK或容器再執行時提供依賴項。例如,在為Java企業應用構建Web應用程式時,您需要將Servlet API和相關Java EE API的依賴關係設定為provided範圍,因為web容器提供了這些類。此作用域僅在編譯和測試類路徑中可用,並且是不可傳遞的。
-
runtime
此範圍指示編譯時不需要依賴項,而執行時需要。它位於執行時和測試類路徑中,而不是編譯路徑中。
-
test
此範圍表明該依賴關係不是應用程式正常使用所必需的,並且僅在測試編譯和執行階段可用。這個分為時不可傳遞的。
-
system
這個總用玉與provided作用域類似,指示必須提供顯式包含它的JAR。專案重視i可用的,不會在儲存庫中查詢。
-
import
這個作用域只支援打包型別為pom的
<dependencyManagement>
標籤部分。它指示將依賴項替換為<dependencyManagement>
標籤中的有效依賴項列表。由於它們被替換,具有匯入範圍的依賴項實際上不參與限制依賴項地傳遞。
每個作用域(import除外)以不同的方式影響傳遞依賴關係,如下表所示。如果將依賴項設定為左列中的範圍,則該依賴項與頂行作用域之間的可傳遞依賴項將導致主專案中的依賴項與交叉點處列出的作用域之間存在依賴關係。如果沒有列出範圍,則表示將忽略依賴項。
compile | provided | runtime | test | |
---|---|---|---|---|
compile | compile(*) | - | runtime | - |
provided | provided | - | provided | - |
runtime | runtime | - | runtime | - |
test | test | - | test | - |
(*)注意:這應該是執行時作用域,因此必須顯式列出所有編譯依賴項。但是,在這種情況下,所依賴的庫從另一個庫擴充套件了一個類,迫使我們在編譯時具有可用性。因此,及時編譯時依賴項是可傳遞的,他們仍然是編譯範圍。
Dependency Management
依賴關係管理部分是一種集中依賴關係資訊的機制。當我們有一組繼承公共父類的專案時,可以將有依賴關係的所有資訊按在公共POM中,使得子專案可以更簡單地引用。通過例子可以很好地說明這種機制。考慮到兩個pom擴充套件了相同的父pom:
Project 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>
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中:
<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>
<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> </project>
注意:在其中兩個依賴項引用中,我們必須指定<type/>
元素。這是因為將依賴項引用與DependencyManagement部分匹配的最小資訊集實際上是groupid、artifactid、type、classifier。多數情況下,這些依賴關係將引用沒有classifier的jar專案,因此type欄位預設為jar,classifier預設為null。
依賴關係管理部分的第二個非常重要的用途是控制可傳遞依賴關係中使用的版本,以這些專案為例:
Project 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>
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>
當maven在專案B版本1.0上執行時,無論其pom中指定的版本如何,都將使用a,b,c和d:
- a和c都被申明為專案的依賴項,由於Dependency mediation的介入,而使用版本1.0.兩者都將具有執行時範圍,因為它是直接指定的
- b在B的父專案依賴管理關係中定義,對於傳遞依賴項,依賴管理優先於依賴中介,所以如果在a或c的pom中引用它,將選擇版本1.0。b也有編譯範圍。
- 最後,由於d在B的依賴關係管理部分中指定,如果d是a或c的依賴關係(或傳遞依賴關係),將選擇1.0版本–再次因為依賴關係管理優於依賴關係中介,也因為當前pom的宣告需要優於其父專案的宣告。
匯入依賴項
上面的示例描述瞭如何通過繼承來指定託管依賴項。但是,在較大的專案中,可能無法實現此目標,因為專案只能繼承單個父專案。為了適應這種情況,專案可以從其它專案導入托管依賴項。這是通過將pom申明為具有import
範圍的依賴項來實現的。
Project 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,最終結果將是相同的。除了d之外,所有A的託管依賴項都將被合併到B中,因為d是在當前pom中定義的。
Project 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>
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>
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,因為X首先被申明,而且在Z的dependencyManagement中沒有申明。
這個過程是遞迴的。例如,如果X匯入了另一個pom,Q。當處理Z時,將顯式所有Q的依賴管理項都在X中定義。
通常當用於定義多個專案構建的一部分的相關工件的“庫”時,匯入最有效。一個專案使用這些庫中的一個或多個工件是相當常見的。但是,有時很難使用與庫中分發的版本同步的工件來保留專案中的版本。下面的模式說明了如何建立“物料清單”(BOM)以供其他專案使用。
專案的根是BOM pom。它定義了將在庫中建立的所有工件的版本。希望使用該庫的其他專案應將此pom匯入其pom的dependencyManagement部分。
<projectxmlns="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。
<projectxmlns="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
<projectxmlns="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> <projectxmlns="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>
下面的專案展示瞭如何在另一個專案中使用這個庫,而不必指定依賴專案的版本。
<projectxmlns="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。沒有辦法解決迴圈,並丟擲異常。
- 當引用其poms具有傳遞依賴性的工件時,專案需要將這些工件的版本指定為託管依賴項。不這樣做會導致構建失敗,因為工件可能沒有指定版本。
System Dependencies
重要提示:此標記已棄用。
範圍系統的依賴關係始終可用,並且不會在儲存庫中查詢。它們通常用於告訴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>