坐標和依賴
坐標詳解
Maven坐標為各種構件(artifact)引入了秩序,每一個artifact都必須明確定義自己的坐標,而一組Maven坐標是由以下元素定義的:
<groupId>com.ssozh.mirana</groupId>
<artifactId>mirana-parent</artifactId>
<packaging>pom</packaging>
<version>1.0.0-SNAPSHOT</version>
- groupId:定義當前maven項目隸屬於的實際項目。
- maven的項目和實際項目不一定是一對一關系,比如SpringFramework這一實際項目,對應的maven項目會有很多包括spring-core、spring-context等等。
- groupId不應該對應項目隸屬的組織和公司,因為一個組織下面會有很多實際項目,如果groupId只定義到組織級別。那么artifactId會非常難以定義
- groupId的表示方式與java包名的表示方式類似,通過與域名反向一一對應。上面的
com.ssozh
是我,mirana
是我定義的實際項目。該groupId應該遇mirana.ssozh.com
對應。
- artifactId:該元素定義實際項目中的一個maven項目。
- 推薦的做法是使用實際項目名稱作為artifactId的前綴,比如上面的離職中artifactId是
mirana-parent
。這樣做的好處是方便尋找實際構建。 - 默認情況下,maven生成的構建,其文件名會議artifactId作為開頭,如
mirana-parent-1.0.0-SNAPSHOT.jar
。這樣就可以方便從一個lib文件夾中找到某個項目的一組構建。考慮有4個項目,每個項目都有core模塊,如果沒有前綴就會看到很多core-1.2.jar
這樣的文件。
- 推薦的做法是使用實際項目名稱作為artifactId的前綴,比如上面的離職中artifactId是
- version:該元素定義maven項目當前所處的版本,如上例中的版本是快照版本。=>13章會詳解。
- packaging:定義maven項目的打包方式。默認使用jar
- classifier:該元素用來幫助定義構建輸出的一些附屬構件。
- 附屬構件與主構件對應,比如javadoc和sources就是一些附屬構件,其包含了java文檔和源代碼。
- 注意:不能直接定義項目的classifier,因為附屬構件倍速項目直接默認生成的,而是有附加的插件幫助完成的。
最后,項目構建的文件名是與坐標相對應的,規則為arifactId-version[-classifier].packaging
。
項目(略)
【因為】本項目不打算直接完成書中的項目,因此這個地方也是先看一遍,等自己的東西做好了,再過來補充這個部分。計划包括:
- OSS:可能不應該這么分,因為OSS無非是一種第三方調用而已。
- common:log4j
- admin:包含controller層的項目。
項目pom
項目的主代碼
項目的測試代碼
構建項目
使用mvn clean install
構建項目,Maven會根據POM配置自動下載鎖所需要的依賴構件,執行編譯、測試、打包等工作。最后將項目生成的構件 安裝到本地倉庫中。這時,該模塊就能供其他Maven項目使用了。
依賴的配置
其實一個依賴聲明可以包含如下一些元素:
<project>
...
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
....
</dependencies>
....
</project>
根元素project
下的dependencies
可以包含一個或多個dependency
元素,以聲明一個或者多個項目依賴。每個依賴可以包含的元素有:
- GAV:坐標。
- type:依賴的類型,對應於項目坐標定義的packaging。大部分情況下該元素省略,默認jar
- scope:坐標的范圍,見下文。
- optional:標記依賴是否可選,見下文
- exclusions:用來排除傳遞性依賴,見下文。
依賴范圍(可感知問題)
JUnit以依賴的范圍是test,測試范圍用元素scope
表示。本節將詳細講解什么是測試范圍,以及各種測試范圍的效果和用途。
首先需要知道,Maven在編譯項目主代碼的時候需要使用一套classpath。比如編譯項目主代碼的時候需要用到spring-core,該文件以依賴的方式被引入到classpath中。
其次,maven在編譯和執行測試的時候會使用另外一套classpath。比如JUnit也會議依賴的方式引入到測試使用的classpath中,不同的是這里的依賴范圍是test。
最后,實際運行maven項目的時候,又會使用一套classpath,上例中的spring-core需要在該classpath中,而JUnit則不需要。
依賴范圍就是用來控制起來與這三種classpath(編譯classpath、測試classpath、運行classpath)的關系。Maven有以下幾種依賴范圍:
-
compile:【默認】編譯依賴范圍。使用此依賴范圍的maven依賴,對於編譯、測試、運行三種classpath都有效。
-
test:測試依賴范圍。使用此依賴范圍的maven依賴,只對測試classpath有效,在編譯主代碼或者運行醒目的使用時將無法使用此類依賴。
-
provided:已提供依賴范圍。使用此依賴范圍的maven依賴,對於編譯和測試classpath有效,但在運行時無效。典型的例子是servlet-api,編譯和測試項目的時候需要該依賴,但在運行項目的時候,由於容器已經提供,就不想要maven重復地引入一遍。
-
runtime:運行時依賴范圍。使用此依賴范圍的maven依賴,對於測試和運行classpath有效,但在編譯主代碼時無效。典型例子是JDBC驅動實現,項目主代碼的編譯只需要JDK提供的JDBC接口,只有在執行測試或者運行項目的時候才需要實現上述接口的具體JDBC驅動。
-
system:系統以來范圍。該依賴於三種classpath的關系,和provided依賴范圍完全能一致,但是使用system范圍的依賴時必須通過systemPath元素顯式地指定依賴文件的路徑。由於此類依賴不是通過maven倉庫解析的,而且常常與本機系統綁定,可能造成構建的不可移植,另外systemPath元素可以引用環境變量,如:
<dependency> <GAV></GAV> <scope>system</scope> <systemPath>${java.home}/lib/rt.jar</systemPath> </dependency>
-
import:導入依賴范圍。該依賴范圍不會對三種classpath產生實際的影響,該范圍依賴只在
dependencyManagement
元素下才有效果,使用該范圍的依賴通常指向一個pom,作用是將目標pom中的dendencyManagement
配置導入並合並到當前POM的dependencyManagement
元素中。- 注意,上述代碼中的依賴的type為pom,import范圍依賴由於其特殊性,一般都指向打包類型的pom的模塊。如果有多個項目,它們使用的依賴版本都是一致的,則就可以定義一個dependencyManagement專門管理依賴的POM,然后在各個項目中導入這些依賴管理配置。
傳遞性依賴
何為傳遞性依賴
【問題】:
考慮一個基於spring框架的項目,如果不使用maven則需要手動下載相關依賴,這么做會引入很多不必要的依賴。
另外一個做法就是懶加載,下載一個只有spring框架的jar包,到實際使用的時候,再根據出錯信息,或者查詢相關文檔,加入需要的其他依賴。
【解決方法】:mavn的傳遞性依賴機制。
有了傳遞性依賴機制,在使用spring的時候就不用考慮它依賴了什么,也不用擔心引入多余的依賴。maven會解析各個直接依賴的POM,將那些必要的間接依賴,以傳遞性依賴的形式傳遞到當前的項目中。
依賴性依賴和依賴范圍
假設A依賴於B,B依賴於C,我們說A對於B是第一直接依賴,B對C是第二個直接依賴,A對C就是傳遞性依賴。第一直接依賴和第二直接依賴的范圍決定了傳遞性依賴的范圍。依賴范圍影響傳遞性依賴:
- | compile | test | provided | runtime |
---|---|---|---|---|
compile | compile | - | - | runtiom |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
第一數列是第一依賴,第一橫列是第二依賴。中間的表格是傳遞范圍。
很明顯可以發現,當第二依賴的范圍是compile的時候,傳遞性依賴的范圍與第一直接依賴范圍一致;當第二依賴范圍是test的時候,依賴不會得以傳遞。
依賴調解
maven引入的傳遞性依賴機制,讓我們不用考慮這些直接依賴引入什么傳遞性依賴。但有時候,當傳遞性依賴造成問題的時候,我們就需要清除地知道該傳遞性依賴是從哪條依賴路徑引入的。
Maven依賴調節(dependency mediation)的第一原則是:路徑最近者優先。
Maven定義了依賴調解的第二原則:第一聲明者優先。在路徑長度一樣情況下,在POM中依賴聲明的順序最靠前的那個依賴優勝。例如:
A->B->Y(1.0)
A->C->Y(2.0)
如果B的依賴聲明C之前,那么Y(1.0)就會被解析使用。
可選依賴
【場景】
假設有這樣一個依賴關系,項目A依賴於項目B,項目B依賴於項目X與項目Y,但是B對於X和Y的依賴都是可選依賴。如果都是compile依賴,那么根據傳遞性,X和Y對於A就是compile范圍的依賴。然而由於X、Y是可選依賴,依賴將不會得以傳遞。
【為什么會有可選依賴】
存在一個項目B實現了兩個特性,其中的特性一依賴於X,特性二依賴於Y,而且這兩個依賴是互斥的,用戶不能同時使用這兩個特性。比如B是一個持久層隔離工具包,他支持多種數據庫,如Myql和postgreSQL等,在構建這個工具包的時候,需要這兩個數據可的驅動程序,但在使用的時候,只會依賴一種數據庫。因為出現了可選依賴
【使用方法】
<project>
<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>
表示這兩個依賴為可選依賴,他們只會對當前項目B產生影響,當其他項目依賴於B的時候,這兩個依賴不會被傳遞。因此當A依賴於B的時候,如果實際使用基於Mysql數據庫,那么再項目A中就需要顯示地聲明mysql-connector-java
這一依賴。
在理想情況下,是不應該使用可選依賴的。在面向對象設計中,有一個單一職責性原則。這個規則在maven項目中也同樣使用。
最佳實踐
排除依賴
傳遞性依賴會給項目隱式地加入很多依賴,這極大的簡化了項目的管理,但是有些時候這種特性也會帶來問題。例如,當前項目有一個第三方依賴,而這個第三方依賴由於某些原因依賴了另外一個類庫的SNAPSHOT版本,那么這個SNAPSHOT就會成為當前的傳遞性依賴,而SNAPSHOT的不穩定性會直接影響到當前的項目。這個時候就需要排除掉該SNAPSHOT,並且在當前項目中聲明該類庫的某個正式發布的版本。具體用法就是用<exclusions>
標簽就行了。
歸類依賴
對於同樣是來自於spring framework的不同模塊,比如spring-core,spring-beans等等。所有這些依賴的版本都是相同的,而且可以預見,如果將來要升級spring framework,這些依賴的版本會一起升級,對於這種情況應該使用<properties>
元素定義maven
屬性,定義一個springframework.version
子元素,其值為2.5.6。然后通過${springframework.version}
替換實際值。
優化依賴
【三個命令】
mvn dependency:list
: 顯示已解析的依賴和每個依賴的范圍。
mvn dependency:tree
:顯示依賴樹
mvn dependency:analyze
:
- used undeclared dependencies指項目中使用到的,但沒有顯式聲明的依賴。【當這個依賴升級了,可能導致當前項目出錯】
- Unused declared dependencies指的是項目中未使用的,但顯示聲明的依賴【需要注意的是,由於該命令只會分析編譯主代碼和測試代碼需要用到的依賴】
補充
classpath
maven項目分為src目錄,resource目錄,test/src目錄,test/resource目錄:
其中src和resource對應到項目的target\classes目錄,如果在src目錄調用classpath,則class的根目錄為target\classes
test/src,test/resource對應到test-classes目錄,如果在test/src目錄調用classpath,則class的根目錄為target\test-classes
maven的classpath就是java下面的地址,和resources下面的地址。因為最后都會編譯/復制到classes中去的。
其實也就是maven中說的主代碼所在的位置
這個地方相關的知識點可以查看:
- java.util.Properties
- org.springframework.util.ResourceUtils