1:依賴聲明
<project> ... <dependencies> <dependency> <groupId>...</groupId> <artifactId>...</artifactId> <version>...</version> <type>...</type> <scope>...</scope> <optional>...</optional> <exclusions> <exclusion> ... </exclusion> ... </exclusions> </dependency> ... </dependencies> ... </project>
解釋說明:
- 根元素prject下的dependencies可以包含一個或所個dependency元素,以聲明一個或多個項目依賴
- groupId、artifactId、version:依賴的基本座標,每一個依賴必須具備的屬性,Maven只有根據這些座標才能找到並下載依賴
- type:依賴的類型,對應於項目座標定義的packaging。大部分情況下,不必聲明,默認爲jar
- scope:依賴的範圍,見下面2:依賴範圍
- optional:標記依賴是否可選,見下面4:依賴可選
- exclusion:用來排除傳遞性依賴,見下面3:傳遞性依賴
2:依賴範圍(用於確認是否可導入依賴包)
Maven在編譯項目主代碼的時候需要使用一套classpath。例如,郵件模塊中的javax.mai依賴會出現
Maven在編譯和執行測試的時候會使用另一套classpath。例如,Juni依賴t只會出現在測試classpath下,javax.mail也會出現
Maven在運行實際項目的時候,又會使用一套classspath。例如,javax.mail需要出現,junit則不需要出現。
依賴範圍就是用來控制依賴與這三種classpath的(編譯classpath、測試classpath、運行classpath)的關係,Maven有以下幾種依賴範圍:
- compile:編譯依賴範圍。如果沒有指定,默認的依賴範圍。此依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效。
- test:測試依賴範圍,此依賴範圍的Maven依賴,只對於測試的classpath有效,在項目編譯主代碼或者運行項目的使用時將無法使用此類依賴。
- provided:已提供依賴範圍,此依賴範圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效,例如SpringBoot生成war包,其中的tomcat依賴就要是provided依賴範圍,在開發中,就要改成compile依賴
- runtime:運行時依賴範圍,此依賴範圍的Maven依賴,對於測試和運行有效,但在編譯主代碼時無效,典型的例子就是JDBC驅動實現,項目主代碼的編譯只需要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才需要實現上述接口。
- 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/rt.jar</systemPath> </dependency>
- import(Maven2.0.9及以上):導入依賴範圍,該依賴範圍不會對三種classpath產生實際的影響。
上述出import以外的各種依賴範圍與三種classpath的關係表:
3:傳遞性依賴
一個基於SpringFramework的項目,如果不使用Maven,就要手動的去添加依賴包,通常是去官網下載,但是裏面的依賴很多,會有不必要的依賴存在。另一種做法就是下載必要依賴包,但是這個包中不包含其他相關依賴,到實際使用的時候,根據報錯引入相關依賴,這種做法是很麻煩的。
Maven的傳遞性依賴機制可以解決這個問題,例如:有一個spring-core的依賴,而在spring-core中也存在自己的依賴,包含了一個commons-logging依賴,見代碼清單:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> <scope>compile</scope> </dependency>
該依賴的依賴範圍是compile。而spring-core的依賴範圍也是cmpile,因此就會出現傳遞性依賴,使得項目也同時依賴commons-logging,如圖所示
4:傳遞性依賴和依賴範圍
針對上圖的傳遞性依賴做一些解釋
Maven project 依賴於 spring-core : 第一直接依賴
spring-core 依賴於 commons-logging :第二直接依賴
Maven project 對於 commons-logging:傳遞依賴
5:依賴調解
並不是所有的傳遞性依賴都能正常工作,當傳遞性依賴造成問題的時候,我們就需要清楚的知道該傳遞性依賴是從哪條依賴路徑引入的。
例如:
- 第一原則:路徑最近者優先:
在項目A中存在這樣的依賴。A –> B –> C –>X(1.0)、A –> D –> X(2.0),X是A的傳遞性依賴,但是這兩條依賴路徑上有兩個版本的X,那麼哪個X會被Maven解析使用呢?在Maven中採用路徑最近者優先策略,因此,X(2.0)會被解析使用
- 第二原則:第一聲明者優先
在針對第一原則下路勁相同,版本不一致的缺陷下提出了第二原則,在項目A中存在這樣的依賴關係。A –> B –> Y(1.0)、A –> C – > Y(1.0),Y的依賴路徑都是2,從Maven2.0.9開始,爲了避免構建對的不確定性,Maven使用了該原則,在依賴路徑相等的情況下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝,如果B的依賴聲明在C之前,那麼Y(1.0)就會被解析使用。
6:可選依賴
假設有這樣一種依賴關係,項目A依賴於項目B,項目B依賴於項目X和項目Y,B對於X和Y的依賴都是可選依賴(optional爲true):A -> B,B -> X(可選),B -> Y(可選)。根據傳遞性依賴的定義,如果所有這三個依賴的範圍都是compile,那麼X,Y就是A的compile範圍傳遞性依賴。然而,由於這裏X,Y是可選依賴,依賴就不會傳遞,也就是說,X,Y將不會對A有任何影響 。
爲什麼要使用可選依賴這一特性呢?可能B項目實現了兩個特性,其中的一個特性一依賴於X,特性二依賴於Y,而且這兩個特性是相互排斥的,用戶不可能同時使用兩個特性,比如B是一個持久層隔離工具包,它支持多種數據庫,包括Mysql,PostgreSQL等,在構建這個工具包時,需要這兩種數據庫的驅動程序,但在使用這個工具包的時候,只會依賴一種數據庫。
在理想情況下,是不應該使用可選依賴的,因爲使用了可選依賴的原因是某一個項目實現了多個特性,在面向對象設計中,有個單一職責性原則,意指一個類應該只有一個職責,而不是糅合太多的功能。這個原則在規劃Maven的時候同樣適用。
7:最佳實踐
- 排除依賴
之所以要排除依賴的原因有以下幾點:
- 依賴不穩定,需要正式版本,多爲引用第三方依賴中包含這個不穩定依賴導致
- 由於版權,依賴不再中央倉庫,需要排除改依賴
上述圖排除依賴的代碼清單
<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>xx.xxx.xx</groupId> <artifactId>A</artifactId> <version>1.0-SNAPSHOT</version> <dependency> <groupId>xx.xxx.xx</groupId> <artifactId>B</artifactId> <version>1.0</version> <!-- 排除C的不穩定依賴 --> <exclusions> <exclusion> <groupId>xx.xx.xx</groupId> <artifactId>C</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入C的穩定依賴 --> <dependency> <groupId>xx.xx.xx</groupId> <artifactId>C</artifactId> <version>1.0</version> </dependency> </project>
代碼清單說明
上述代碼中,項目A依賴於項目B,但是由於一些原因,不想引入項目B中的依賴C,而是自己顯示的聲明對於項目C穩定版本1.0的依賴。所以在項目B中使用exclusions對依賴進行排除,由英語語法的單複數可知,在exclusions中可包含多個exclusion,即可排除多個依賴。需要注意的是,聲明exclusion的時候只需要groupId和artifactId,而不需要version元素,這是因爲只需要groupId和artifactId就能唯一定位依賴圖中的某個依賴。也就是說,在Maven依賴中,不可能出現groupId和artifactId相同但是version不同的兩個依賴。
- 歸類依賴
之所以使用歸類依賴,是爲了保持版本的一致性,排除出錯,例如我們通常在整合Spring Framework的時候,有很多都是相同版本的,對此,Maven也提供了歸類依賴的方式簡單管理版本,這樣做的好處就是,將來在升級版本的時候,只需要改這個歸類,不需要改所有的依賴版本了。這個就和Java中的常量是一個意思,在Maven中通過EL表達式替換。
歸類依賴的語法:
<properties>
<springframeword.version>4.1.5</springframeword.version>
</properties><!— 使用 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframeword.version}</version>
</dependency> - 優化依賴
優化依賴是指:對項目的依賴進行優化,去除多餘的依賴,顯式的聲明某些必要的依賴
使用mvn dependency:list查看當前項目的已解析依賴,依賴範圍
使用mvn dependency:tree查看當前的依賴樹,可以清晰的看到依賴的引入路徑
使用mvn dependency:anapyze分析依賴,只會分析編譯主代碼和測試代碼需要用到的依賴,一些執行測試和運行時需要的依賴就發現不了。