Maven編譯、測試、運行會使用不一樣的classpathjava
Maven再編譯、測試、運行時會使用三套classpath (編譯classpath、測試classpath、運行classpath)mysql
Maven有一下幾種依賴範圍程序員
compilespring
testsql
provide數據庫
runtimeapi
systemmaven
import (Maven 2.0.9 及以上)ide
compile是指?函數
編譯依賴範圍,若是沒有指定,就會默認使用該依賴範圍,使用此依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效,典型的例子是spring-core,在編譯、測試和運行的時候都須要使用該依賴
test是指?
測試依賴範圍,使用此依賴範圍的Maven依賴,只對於測試classpath有效,在編譯主代碼或者運行項目的使用時將沒法使用此類依賴,典型的例子時JUnit。它只有在編譯測試代碼及運行測試的時候才須要
provided是指?
已提供依賴範圍,使用此依賴範圍的Maven依賴,對於編譯和測試classpath有效,但在運行時無效。典型的例子時servlet-api,編譯和測試項目的時候須要該依賴,但在運行項目的時候,因爲容器已經提供,就不須要Maven重複地引入一遍
runtime是指?
運行時依賴範圍,使用此依賴範圍的Maven依賴,對於測試和運行classpath有效,但在編譯主代碼時無效。典型的例子時JDBC驅動實現,項目主代碼的編譯只須要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才須要實現,上述接口的具體JDBC驅動
system是指?
系統依賴範圍,該依賴與三種classpath的關係,和provided依賴範圍徹底一致。可是,使用system範圍的依賴時必須經過systemPath元素顯式地指定依賴文件的路徑,因爲此類以來不是經過Maven倉庫解析的,並且每每與本機系統綁定,可能形成構建的不可移植,所以應該謹慎使用。systemPath元素能夠引用環境變量如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artofactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</depemdency>
import是指?
導入依賴範圍,該依賴範圍不會對三種classpath產生實際的影響,後面會講
上述除了import之外的各類依賴範圍與三種classpath的關係
依賴範圍 (Scope) |
對於編譯 classpath有效 |
對於測試 Classpath有效 |
對於運行時 Classpath有效 |
例子 |
Compile |
Y |
Y |
Y |
Spring-core |
Test |
—— |
Y |
—— |
JUnit |
Provided |
Y |
Y |
—— |
Servlet-api |
Runtime |
—— |
Y |
Y |
JDBC驅動實現 |
System |
Y |
Y |
—— |
本地的,Maven倉庫以外的類庫文件 |
在項目中手動引入依賴的問題
考慮一個基於Spring Framework的項目,若是不使用Maven,那麼在項目中就須要手動下載相關依賴,因爲Spring Framework又會依賴於其餘開源類庫,所以實際中每每會下載一個很大的如spring-framework-2.5.6-with-dependencies.zip的包,這裏包含了全部Spring Framework的jar包,以及全部它依賴的其餘jar包。這麼作每每就引入了不少沒必要要的依賴。
另外一種作法是隻下載spring-framework-2.5.6.zip這樣的包,這裏不包含其餘相關依賴,到實際使用的時候,在根據出錯信息,或者相關文檔,加入須要的其餘依賴,很顯然,這也是一件很是麻煩的事情。
Maven的傳遞性依賴機制能夠很好地解決這一問題
若是一個項目有一個org.springframework:spring-core:2.5.6的依賴,而實際上spring-core也有它本身的依賴
咱們能夠直接訪問位於中央倉庫的該構建的:
POM:http://repo1.maven.org/maven2/org/springframework/spring-core/2.5.6/spring-core-2.5.6.pom,該文件包含了一個commons-logging依賴:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
account-mail有一個compile範圍的spring-core依賴,spring-core有一個compile範圍的commons-logging依賴,那麼commons-logging就會成爲account-email的compile範圍依賴,commons-logging就是account-email的一個傳遞性依賴
傳遞性依賴機制的邏輯是?
Maven會解析各個直接依賴的POM,將那些沒必要要的間接依賴,以傳遞性依賴的形式引入到當前的項目中
依賴範圍也影響傳遞性依賴
依賴範圍不只能夠控制依賴於三種classpath的關係,還對傳遞性依賴產生影響
好比,account-email對於spring-core的依賴範圍是compile,spring-core對於commons-loggin的依賴範圍是compile,那麼account-email對於commons-logging這一傳遞性依賴的範圍也就是compile
什麼是第一直接依賴,第二直接依賴?
假設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 |
最左邊的一行表示第一直接依賴範圍,最上面一行表示第二直接依賴範圍,中間的交叉單元格則表示傳遞性依賴範圍
仔細觀察上表發現規律
1、當第二直接依賴的範圍是compile的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致
2、當第二直接依賴的範圍是provided的時候,只傳遞第一直接依賴範圍也爲provided的依賴,且傳遞的範圍一樣爲provided
3、當第二直接依賴的範圍是runtime的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致,但compile例外,此時傳遞性依賴的範圍爲runtime
傳遞性依賴機制的問題
Maven引入的傳遞性依賴機制,一方面大大簡化和方便了依賴聲明,另外一方面,大部分狀況下咱們只須要關心項目的直接依賴是什麼,而不用考慮這些直接依賴會引入什麼傳遞性依賴。可是有時候,當傳遞性依賴形成問題的時候,咱們就須要清楚地知道該傳遞性依賴是從那條依賴路徑引入的
如何發現傳遞性依賴的問題?
好比,項目A有這樣的依賴關係: A -> B -> C -> X (1.0)、A->D->X(2.0),X是A的傳遞性依賴,可是兩條依賴路徑上有兩個版本的X,那麼哪一個X會被Maven解析使用呢?
兩個版本都被解析顯然是不對的,由於那會形成依賴重複,所以必須選擇一個。
Maven依賴調解的第一原則
Maven依賴調解 (Dependency Mediation) 的第一原則是:路徑最近者優先,上面的例子中X(1.0)的路徑長度爲3,而X(2.0)的路徑長度爲2,所以X(2.0)會被解析使用
依賴調解第一原則不能解決全部問題
好比這樣的依賴關係,A->B->Y(1.0) A->C->Y(2.0),Y(1.0)和Y(2.0)的依賴路徑長度是同樣的,都爲2,那麼到底誰會被解析使用呢?
Maven2.0.8及以前的版本
在Maven2.0.8及以前的版本,這是不肯定的,解決不了。哦NO
Maven2.0.9版本開始
從Maven2.0.9開始,爲了儘量避免構建的不肯定性,Maven定義了依賴調解的第二原則
什麼是依賴調解的第二原則?
Maven的依賴調解第二原則是,第一聲明者優先,在依賴路徑長度相等的前提下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝,上面的例子中,若是B的依賴聲明在C以前,那麼Y(1.0)就會被解析使用
什麼是可選依賴?
假設有這樣一個依賴關係,項目A依賴於項目B,項目B依賴於項目X和Y,B對於X和Y的依賴都是可選依賴:A->B、B->X(可選)、B->Y(可選)。根據傳遞性依賴的定義,若是全部這三個依賴的範圍都是compile,那麼X、Y就是A的compile範圍傳遞性依賴。然而,因爲這X、Y是可選依賴,依賴將不會得以傳遞。換句話說,X、Y將不會對A有任何影響
那麼,爲何要使用可選依賴這一特性呢?
可能項目B實現了兩個特性,其中的特性一依賴於X,特性二依賴於Y,並且這兩個特性是互斥的,用戶不可能同時使用兩個特性。
能不能具體說明一下兩個特性互斥是什麼意思?
好比B是一個持久隔離工具包,它支持多種數據庫,包括MySQL、PostgreSQL等,在構建這個工具包的時候,須要這兩種數據庫的啓動程序,但在使用這個工具包的時候,只會依賴一種數據庫。
項目B的依賴聲明以下
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0<version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10<version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId> postgresql </artifactId>
<version>8.4-701.jdbc3<version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
上面的XML代碼片斷中,使用<optional>元素表示mysql-connector-java和postgresql這兩個依賴爲可選依賴,它們只會對當前項目B產生影響,當其餘項目依賴於B的時候,這兩個依賴不會被傳遞。所以,當項目A依賴於項目B的時候,若是其實際使用基於MySQL數據庫,那麼在項目A中就須要顯式地聲明mysql-connector-java這一依賴
項目a的XML代碼片斷
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0<version>
<dependencies>
<dependency>
<groupId> com.juvenxu.mvnbook </groupId>
<artifactId> project-b </artifactId>
<version>1.0.0<version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10<version>
</dependency>
</dependencies>
</project>
最後強調幾點
1、在理想的狀況下,是不該該使用可選依賴的
2、使用可選依賴的緣由是某一個項目實現了多個特性,可是違背了單一職責原則
3、規劃Maven項目的時候也應該遵循這個原則
更好的作法是?
1、爲MySQL和PostgreSQL分別建立一個Maven項目,基於一樣的groupId分配不一樣的artifactId
2、如com.juvenxu.mvnbook:project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql
3、在各自的POM中聲明對應的JDBC驅動依賴,並且不使用可選依賴,用戶根據須要選擇使用project-b-mysql或者project-b-postgresql,因爲傳遞性依賴的做用,就不用再聲明JDBC驅動依賴了
最佳實踐
傳遞性依賴帶來的兩個問題
1、當項目有一個第三方依賴,而這個第三方依賴因爲某些緣由依賴了另一個類庫的SNAPSHOT版本,那麼這個SNAPSHOT就會成爲當前項目的傳遞性依賴,而SNAPSHOT的不穩定性會直接影響到當前的項目。這時就須要排除掉該SNAPSHOT,而且在當前項目中聲明該類庫的某個正式發佈的版本
2、你可能想要替換某個傳遞性依賴,好比Sun JTA API,Hibernate依賴於這個JAR,可是因爲版權的因素,該類庫不在中央倉庫中,而Apache Gernimo項目有一個對應的實現。這時你就能夠排除Sun JTA API,再聲明Geronimo的JTA API實現
關於排除掉某個傳遞性依賴如何操做?
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0<version>
<dependencies>
<dependency>
<groupId> com.juvenxu.mvnbook </groupId>
<artifactId> project-b </artifactId>
<version>1.0.0<version>
<exclusions>
<exclustion>
<groupId> com.juvenxu.mvnbook </groupId>
<artifactId> project-c </artifactId>
</exclustion>
</exclustions>
</dependency>
<dependency>
<groupId> com.juvenxu.mvnbook </groupId>
<artifactId> project-c </artifactId>
<version>1.1.0<version>
</dependency>
</dependencies>
</project>
上面的代碼,項目A依賴於項目B,但因爲一些緣由,不想引入傳遞性依賴C,而是本身顯式地聲明對於項目C 1.1.0 版本的依賴。
代碼中使用exclusions元素聲明排除依賴,exclusions能夠包含一個或者多個exclusion子元素,所以能夠排除一個或者多個傳遞性依賴
須要注意的是,groupId和artifactId就能惟必定位依賴圖中的某個依賴
若是要升級依賴的版本
考慮這樣一種場景,有不少關於Spring Framework的依賴
它們分別是:
org.springframework:spring-core:2.5.6
org.springframework:spring-beans:2.5.6
org.springframework:spring-context:2.5.6
org.springframework:spring-context-support:2.5.6
它們是來自同一項目的不一樣模塊,所以,全部這些依賴的版本都是相同的,並且能夠預見
若是未來須要升級Spring Framework,這些依賴的版本會一塊兒升級,這是個問題,全部的version都得改一遍,這種狀況在Java中似曾相識
若是說技術是想通的話,那麼看一下爲何java會喜歡使用常量?
考慮
public double c(double r) {
return 2 * 3.14 * r
}
public double s(double r) {
return 3.14 * r * r
}
上面兩個函數計算圓的周長和麪積,稍微有經驗的程序員一眼就會看出一個問題,使用字面量(3.14)顯然不合適,應該使用定義一個常量並在方法中使用
public final double PI = 3.14;
public double c(double r) {
return 2 * PI * r
}
public double s(double r) {
return PI * r * r
}
使用常量不只讓代碼變得更加簡潔,更重要的是能夠避免重複,在須要更改PI的值的時候,只須要修改移除,下降了錯誤發生的機率。
同理應用與Maven
對於account-mail中的Spring Framework來講,也應該在一個惟一的地方定義版本,而且在dependency聲明中引用這一版本,這樣,在升級Spring Framework的時候就只須要修改一處
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>account-email</artifactId>
<name>Account Email</name>
<version>1.0.0<version>
<projecties>
<springframework.version>2.5.6</springframework.version>
</projecties>
<dependencies>
<dependency>
<groupId>org.springframwork</groupId>
<artifactId>spring-core</artifactId>
<version>${ springframework.version }<version>
</dependency>
<dependency>
<groupId>org.springframwork</groupId>
<artifactId>spring-beans</artifactId>
<version>${ springframework.version }<version>
</dependency>
<dependency>
<groupId>org.springframwork</groupId>
<artifactId>spring-context</artifactId>
<version>${ springframework.version }<version>
</dependency>
<dependency>
<groupId>org.springframwork</groupId>
<artifactId>spring-context-support</artifactId>
<version>${ springframework.version }<version>
</dependency>
</dependencies>
</project>
解釋上面的代碼
這裏簡單用到了Maven屬性,首先使用properties元素定義Maven屬性,該例中定義了一個springframework.version子元素,其值爲2.5.6
有了這個屬性定義以後,Maven運行的時候會將POM中的全部的${springframework.version}替換成實際值2.5.6,也就是說,可使用美圓符號和大括號環繞的方式引用Maven屬性。而後,將全部Spring Framework依賴的版本值用這一屬性應用表示。這和在Java中用常量PI替換3.14是一樣的道理,不一樣的只是語法
什麼是Maven的屬性?
優化依賴
在軟件開發過程當中,程序員會經過重構等方式不斷地優化本身得代碼,使其變得更簡潔、更靈活。同理,程序員也應該可以對Maven項目的依賴瞭然於胸,並對其進行優化,如去除多餘的依賴,顯式地聲明某些必要得依賴
什麼是已解析依賴(Resolved Dependency)?
Maven會自動解析全部項目的直接依賴和傳遞依賴,而且根據規則正確判斷每一個依賴的範圍,對於一些依賴衝突,也能進行調節,以確保任何一個構件只有惟一的版本在依賴中存在,在這些工做以後最後獲得的那些依賴被稱爲一解析依賴
查看當前項目的已解析依賴的命令有
mvn dependency:list
mvn dependency:tree
mvn dependency:analyze
關於mvn dependency:analyze命令須要注意的是?
mvn dependency:analyze工具能夠幫助分析當前項目的依賴
1、Used undeclared dependencies 指項目中使用到的,但沒有顯式聲明的依賴,這種依賴是經過直接依賴傳遞進來的,當升級直接依賴的時候,相關的傳遞性依賴的版本也可能發生變化,這種變化不易察覺,可是有可能致使當前項目出錯。例如因爲接口的改變,當前項目中的相關代碼沒法編譯,這種隱藏的、潛在的威脅一旦出現,就每每須要耗費大量的時間來查明真相,所以,顯式聲明任何項目中直接用到的依賴
2、Unused declared dependencies,意指項目中未使用的,但顯式聲明的依賴,對於這類依賴不該該簡單地直接刪除其聲明,而應該仔細分析,因爲dependency:analyze只會分析編譯主代碼和測試代碼須要用到的依賴,一些執行測試和運行時須要的依賴它發現不了。