一:依賴范圍
Maven在編譯項目主代碼的時候需要使用一套classpath。其次,在編譯和執行測試的時候會使用另外一套classpath。最后,實際運行Maven項目的時候,又會使用一套classpath。
所謂的依賴范圍就是用來控制依賴與這三種classpath(編譯、測試、運行)的關系,Maven有以下幾種依賴范圍:
- compile:編譯依賴范圍。如果沒有指定,默認使用該依賴范圍。使用此依賴范圍時,對於編譯、測試、運行都有效。例如:spring-core,編譯、測試、運行時都需要使用該依賴。
- test:測試依賴范圍。只對測試classpath有效。例如:JUnit,它只在編譯測試代碼以及運行測試的時候才需要,編譯和運行classpath時無法使用此依賴。
- provided:已提供依賴范圍。對於編譯和測試時有效,但在運行時無效。例如:servlet-api,編譯和測試項目的時候需要該依賴,但運行時,由於容器已經提供,就不需要Maven重復的引入。
- runtime:運行時依賴。編譯時無效,對於測試和運行有效。例如:JDBC驅動實現,編譯時只需要JDK提供的JDBC接口,只有在執行測試和運行時才需要實現上述接口的具體JDBC驅動。
- system:系統依賴范圍。同provided。使用該依賴時必須通過systemPath元素顯式地指定依賴文件路徑。主要用於依賴本地的、且Maven倉庫之外的類庫文件。例如:
<dependency> <groupId>javax.sql</groupId> <artifactId>jdbc-stdext</artifactId> <version>2.5</version> <scope>system</scope> <systemPath>${spath}/lib/test.jar<systemPath> </dependency>
二:傳遞依賴和依賴范圍
當我們依賴一個a.jar時,如果a.jar依賴b.jar,那么只需要早pom中聲明對a.jar的依賴即可,b.jar會被Maven自動加載進來。
例如:有一個org.springframework:spring-core:2.5.6的依賴,而實際上spring-core也有它自己的依賴,它依賴commons-logging。有了傳遞依賴機制,在使用spring-core時不需要考慮它依賴了什么。Maven會自動解析。
依賴范圍在傳遞依賴時會略有變化
當第二直接依賴的范圍是compile的時候,傳遞性依賴的范圍與第一直接依賴的范圍一致;
當第二直接依賴的范圍是test的時候,依賴不會得以傳遞;
當第二直接依賴的范圍是provided的時候,只傳遞第一直接依賴范圍也為provi的依賴,且范圍為provided;
當第二直接依賴的范圍是runtime的時候,傳遞性的依賴范圍與第一直接依賴的范圍一致,但compile例外,此時傳遞依賴范圍為runtime;
三:依賴調解
- 原則一:路徑最近者優先。例如:A ->B ->C ->X(1.0) 同時 A ->D ->X(2.0),很顯然X(2.0)路徑更短,會被解析使用。
- 原則二:第一聲明者優先。在依賴長度相等情況下,解析在pom中依賴聲明中順序考前的。例如:A ->B ->X(1.0) 同時 A ->D ->X(2.0)。如果B在D之前聲明,那么X(1.0)會被解析。
除以上兩種原則外,還可以手動排除,例如:A ->B ->X(1.0)同時A ->X(2.0)。如果項目A希望加載X(2.0)可做如下聲明,通過<exclusion>元素來顯式排除。
<dependency> <groupId>com.xxx.xx</groupId> <artifactId>xx-B</artifactId> <version>2.5</version> <exclusions> <exclusion> <groupId>com.xx</groupId> <artifactId>project-X</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.xxx.xx</groupId> <artifactId>project-x</artifactId> <version>2.0</version> </dependency>
四:可選依賴
例如:b.jar是一個持久層工具包,它同時支持Mysql和PostgreSql,A項目依賴b.jar,那么在構建A時需要這兩種數據庫的驅動程序,但在使用的時候知會依賴一種數據庫。A項目的依賴聲明如下:
<dependency>
<groupId>com.xxx.xx</groupId>
<artifactId>xx.db</artifactId>
<version>2.5</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>
</dependency>
使用<optional>元素表示這兩個為可選依賴,這是依賴不會傳遞到A項目,當A項目需要使用基於MySQL數據庫時,需要顯式聲明對mysql的依賴。
另外:在理想的情況下是不會出現這種情況的,因為在面向對象設計中,有一個單一職責原則,即一個jar的職責應該只有一個。所以對於b.jar,最好是創建2個Maven項目,分別實現mysql和postgresql。
五:依賴優化
代碼需要不斷重構才能達到最優,依賴管理也是一樣,需要不斷的進行去除多余依賴,以及顯式的聲明某些必要的依賴。
Maven會自動解析所有項目的直接依賴和傳遞依賴,並根據規則判斷每個依賴的范圍,對於一些依賴沖突也能進行調節,這些工作之后得到這個項目的完整的已解析依賴。
可通過運行以下命令查看當前項目的已解析依賴:
mvn dependency:list
上圖展示了當前項目中所有已解析的依賴,同時每個依賴的范圍也得以明確標示。
如果將直接在pom中聲明的依賴定義為第一層依賴,這些頂層依賴的依賴定義為第二層依賴,則以此類推可以形成一個完整的依賴樹。
可運行以下明細查看當前項目的依賴樹
mvn dependency:tree
從上圖中可以清晰看出,雖然沒有聲明slf4j-api,但它通過傳遞依賴被加載進來,其范圍為compile。
可運行以下命令對當前項目依賴進行簡單分析
上圖中Used undeclared dependencies,表示項目中使用到的,但是沒有顯式聲明的依賴。可以看到第一個依賴是SNAPSHOT版本,它是通過傳遞依賴被加載進來的,這種依賴就是項目中的隱藏的、潛在的炸彈,因為引用的是SNAPSHOT非穩定版本,且不在pom中顯式聲明,很容易被忽略。該炸彈一旦爆炸,往往需要耗費大量時間來查明。
還有一個Unused declared dependencies,表示項目中未使用的,但是顯式聲明的依賴。如果真的不需要,建議去除聲明。但需要注意的是,dependency:analyze知會分析編譯和測試時需要用到的依賴,一些運行時依賴就無法被發現。所以在優化依賴時一定要小心測試。