maven詳解之坐標與依賴
看著簡單而又複雜的pom.xml文件,看似熟悉,當自己編寫的時候覺得簡單,但是看人家項目的時候又覺得複雜的很,現在我們一起來分析這個pom文件。
Maven的坐標為各種構件引入了秩序,任何一個構件都必須明確的定義自己的坐標,maven的坐標包括如下的元素:
groupId: 定義當前Maven項目隸屬的實際項目
artifactId: 該元素定義實際項目中的一個Maven項目或模塊
version: 該元素定義Maven項目當前所處的版本
packaging: 該元素定義Maven項目的打包方式
classifier: 該元素用來幫助定義構建輸出的一些附屬構件
注:groupId、artifactId、version、packaging是必須定義的,classifier是不能被直接定義的,因為附屬構件不是項目直接默認生成的,而是由附加的插件幫助生成的。
元素詳解:
根元素project下的dependencies元素詳解:
dependencies可以包含一個或者多個dependency元素,以聲明一個或多個項目依賴, 其包含的元素:
groupId、artifactId、version:依賴的基本坐標,對於任何一個依賴來說,基本的坐標是最重要的,Maven是根據坐標來找到需要的依賴
type: 依賴的類型
scope: 依賴的範圍
optional: 標記依賴是否可選(參見可選性依賴)
exclusions: 用來排除傳遞性依賴(參見依賴的傳遞性)
依賴範圍詳解:
Maven在編譯項目主代碼的時候需要使用一套classpath
Maven在編譯和執行測試的時候會使用另外一套classpath
Maven在實際運行項目的時候又會使用一套classpath
依賴範圍就是用來控製依賴與這三種classpath(編譯classpath、測試classpath、運行classpath)的關係
Maven的6種依賴範圍:
compile: 編譯依賴範圍(默認),對於編譯、測試、運行三種classpath都有效
test: 測試依賴範圍, 隻對測試classpath有效。典型範例:Junit
provided: 已提供依賴範圍 對於編譯和測試classpath有效,但在運行時無效。典型範例:servlet-api
runtime: 運行時依賴範圍 對於測試和運行classpath有效,但在對編譯主代碼時無效。典型範例:JDBC
system: 係統依賴範圍
import: (maven2.0.9及以上): 導入依賴範圍,它不會對三種實際的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倉庫之外的類庫文件 |
了解了依賴的基本元素和依賴範圍之後,我們會發現在我們項目中經常會出現一些默認的配置問題,導致編譯和運行失敗的情況,現在讓我們來學習如何解決這些問題,首先要了解一下依賴的傳遞性
傳遞性依賴和依賴範圍
簡單的說,一般項目中出現問題多數是因為重複的引用或者引用了較低版本的依賴,或者是他們的依賴範圍發生了變化。
舉個例子來理解傳遞性依賴:
我們創建了一個Maven Project-----learnDependency,然後我們引入了spring-core這個依賴,然後我們打開spring-core的pom.xml發現,spring-core也有自己的依賴:commons-logging,而且該依賴沒有聲明依賴範圍,那麼默認的就是compile,所以這時我們就可以說:commons-logging也是learnDependency的一個依賴,這時我們就將這種依賴稱之為傳遞性依賴,commons-logging是learnDependency的一個傳遞性依賴。有了傳遞性依賴,我們就可以在使用的時候不去考慮我們引入的依賴到底是否需要其它依賴,和是否引入多餘的依賴,Maven 會解析各個直接依賴的pom,將必要的間接依賴引入到項目中。
細說傳遞性依賴
假設:A依賴於B,B依賴於C,那麼我們就說A對於B是第一直接依賴,B對於C是第二直接依賴,A對於C是傳遞性依賴。
因為依賴是有依賴範圍的,那麼對於這種傳遞性依賴Maven又是如何界定其依賴範圍的呢?
當第二直接依賴的範圍是compile的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致;
當第二直接依賴的範圍是test的時候,依賴不會得以傳遞
當第二直接依賴的範圍是provided的時候,隻傳遞第一依賴範圍也為provided的依賴,且傳遞性依賴的範圍同樣是provided;
當第二直接依賴的範圍是runtime的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致,但compile除外,此時傳遞性依賴範圍為runtime
compile | test | provided | runtime | |
---|---|---|---|---|
compile | compile | runtime | ||
test | test | test | ||
provided | provided | provided | provided | |
runtime | runtime | runtime |
左側第一列表示第一直接依賴範圍,最上麵一行表示第二直接依賴
在我們了解了Maven強大的依賴機製之後,我們開始解決問題:
常見問題一:依賴的重複引入
之前說過Maven可以有效的解決依賴的重複引入問題,但是為什麼我們在項目還會出現這類問題呢?先讓我們來看一下Maven是如何處理重複引入問題的:
情景一:我們在項目中分別引入了2個依賴A和B,A又依賴的C,C又依賴了D,B也依賴了D,但是這個時候C依賴的D和B依賴的D的版本是不同的:
項目----A---C----D
項目----B---D
也就是說,當前項目引入了2次D依賴,那麼這時,Maven將采用第一原則:路徑最近原則
情景二:我們在項目中分別引入了2個依賴A和B,而A和B又都引入了C,但是,此時A依賴的C和B依賴的C版本是不一致的,那麼這個時候Maven如何處理呢?
這時,第一原則已經不起作用了,
在Maven2.0.8及之前的版本中 和 Maven2.0.9之後的版本Maven對於這種情況的處理方式是不一致的
確切的說:
在Maven2.0.8及之前的版本中Maven究竟會解析哪個版本的依賴,這是不確定的
在Maven2.0.9之後的版本中,製定了第二原則:第一聲明者優先
就是說,它取決於在POM中依賴聲明的順序
這個問題就說明了,為什麼我們常常遇到的可以正常運行的項目,然後我們增加了一個看似無關的依賴,然後項目就出現了錯誤,就是這個傳遞性依賴搞的鬼!
還要補充說明的一種情況是可選依賴
為什麼會有可選依賴呢?是因為某一個項目實現了多個特性,但是我們在麵向對象的設計中,有一個原則叫:單一職責性原則,就是強調在一個類隻有一項職責,而不是糅合了太多的功能,所以一般這種可選依賴很少會出現。
常見問題二:默認引入的依賴----第二直接依賴的版本過低或者依賴了不穩定的快照
這個問題我們在開發中也經常遇到,在某個第二直接依賴中引入了1.0版本,但是我們現在想使用2.0版本,這時我們要如何解決?
引入一個名詞:排除依賴,也可以叫替換依賴
想實現依賴排除,然後替換成自己想要的依賴,這時我們要用到的一個配置是<exclusions>和<exclusion>,我們可以使用這一元素聲明排除依賴,然後顯示的聲明我們想要的依賴,在<exclusions>中可以聲明一個或多個<exclusion>來排除一個或多個傳遞性依賴。
注:聲明<exclusion>的時候隻需要聲明groupId和artifactId就能唯一定位依賴圖中的某個依賴。
A -------> B ------×----C(version1.0)
|
|
C(version2.0)
常見問題三:解決重複的配置
我們在開發中也經常遇到這樣的情況,比如在使用spring framework的時候,他們都是來自於同一個項目的不同模塊,因此這些依賴的版本都是相同的,而且在將來升級的時候,這些版本也會一起被升級,這時Maven又提供了一種解決方案------使用properties元素定義Maven屬性,然後引用。
示例:
<properties> <springframework.version>2.5.6</springframework.version> </properties>這個時候我們就可以在聲明依賴的時候使用${springframework.version}來替換具體的版本號
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${springframework.version}</version> </dependency>
如何正確的優化依賴
首先我們必須要對maven的依賴處理方式了然於胸,然後我們就可以去除多餘的依賴,顯示的聲明必要的依賴,保證每個構件都隻有唯一的版本在依賴中存在
使用命令來查看當前項目的已解析依賴:
mvn dependency : list
經過Maven解析之後,就會構成一個依賴樹
也可以使用命令查看當前項目的依賴樹:
mvn dependency : tree
使用命令分析當前當前項目的依賴:
mvn dependency : analyze
該命令執行結果的兩個重要部分:
Used undeclared dependencies: 表示項目中使用到的,但是沒有顯示聲明的依賴
Unused declared dependencies: 表示項目中未使用的,但顯示聲明的依賴
注:dependency : analyze隻會分析編譯主代碼和測試代碼需要用到的依賴,一些執行測試和運行時需要的依賴它無法發現。
對於項目中的最佳實踐,需要自己多多的嚐試或者看別人的一些分享,這樣對於開發效率會有很大的幫助,當然在項目開發的過程中不斷的優化和調整這種方法也未嚐不可。
古人說:“覆水難收”。講話就像潑水,潑出去的水無法再收回,講過的話也一樣收不回來,所以一句話要出口以前,不能不慎思。講話是一門藝術,即使講好話,也要顧慮不能「洗臉礙了鼻子」,你講這個人好,得罪了那個人,話就講得不夠高明了。講不好的話,讓雙方聽了都不高興,當然就更不能講了。
最後更新:2017-04-03 05:39:44