身邊有幾位剛使用Maven的同學表示——在一個叫"pom.xml"的文件里聲明一個依賴就不用去手動添加jar了,感覺這東西和自己手動管理依賴沒太大區別。
當然,並不是這樣,在此記錄dependency那些事兒。
dependency
一個依賴可以按照maven的坐標標准進行定義。
比如:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
上面是最常見的坐標屬性,偶爾也會看到有classifier。
下面簡單說明一下標簽:
- groupId:通常用作項目標識,比如SpringFramework項目的groupId是org.springframework。
也可能會用作項目隸屬的組織的名稱,但組織下有多個項目則不好分辨,盡量保證層次級別明確。 - artifactId:級別低於groupId的項目標識,通常用於標識一個module,比如spring-aop。
- classifier:用於標識隸屬於module的附件。
- version:當前項目版本,版本的寫法應有相應的規范。
- type:依賴的類型,通常不需要聲明,默認為jar。
- scope:依賴范圍,默認為compile。
- packaging:打包方式,默認為jar。
- optional:依賴是否可選。
- exclusions:排除傳遞性依賴。
scope
引入Junit依賴時通常需要聲明test scope:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
大概能猜出是測試時使用該依賴,但不完全正確。
Maven編譯代碼時需要使用classpath,但classpath不止一種,而是:
- 編譯classpath
- 測試classpath
- 運行classpath
Maven會根據需要使用不同的classpath,而scope可以用來控制依賴與這三種classpath之間的關系。
- compile:默認使用該項,對三種classpath都有效。
- test:僅對測試classpath有效,如上面的Junit。
- provided:對編譯和測試classpath有效,比如開頭貼出的servlet-api的依賴,會在運行時由app server提供,Maven不可以重復引入。
- runtime:對測試和運行classpath有效,編譯時無效。
- system:對編譯和測試classpath有效,但必須通過systemPath顯示指定依賴文件的位置,可以使用系統環境變量。
- import:不針對任何一種classpath,該項用於導入其他pom中的dependencyManagement元素。
通常會使用前三種。
scope不僅用來控制依賴與classpath之間的關系,還會對依賴的傳遞性產生影響。
傳遞性依賴? 比如A依賴B,B依賴C,則A對於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 |
考慮一下這樣的依賴關系,A-> C -> D(1.0)和A-> B -> D(2.0)
此時應該如何處理? 引入兩種D依賴是不可能的。
Maven有依賴調節原則:
- 路徑最近優先。
- 當路徑長度相同,聲明順序優先。
對於上面的例子,A-> C -> D(1.0)和A-> B -> D(2.0)的路徑長度相同,但前者聲明早於后者,因此加入的傳遞性依賴則是D(1.0)。
另外,還需要考慮這樣一個場景。
A依賴B、B依賴X和Y,X和Y都是可選依賴,即<optional>true</optional>,且4個都是compile。
此時,X和Y則不會被傳遞,對A是不可見的。
exclusions
但依賴調節並不解決所有問題,我們還需要exclusions來進行排除依賴。
例如這樣一個情況,工程中引入了A,A依賴B,但是B的版本過舊。
此時可以使用exclusions排除該傳遞性依賴,並顯示聲明一個最新版本的B依賴。
比如這樣:
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>4.2.1</version>
<exclusions>
<exclusion>
<artifactId>bcmail-jdk14</artifactId>
<groupId>bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bcprov-jdk14</artifactId>
<groupId>bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>bctsp-jdk14</artifactId>
<groupId>bouncycastle</groupId>
</exclusion>
</exclusions>
</dependency>
properties
用於統一管理屬性,比如我們引入很多spring framework:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.0.3 RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.3 RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.0.3 RELEASE</version>
</dependency>
可見版本都是一樣的,想更改版本時再一個一個修改太麻煩。
properties可以解決這一問題:
<properties>
<spring.version>4.0.3.RELEASE</spring.version>
</properties>
引入spring可以改為:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
這樣確實簡介了不少,但你可能仍然討厭XML。
興許,以后我們不會再用XML寫構建文件。
我們可能會用一些插件(比如Polyglot for Maven)或者其他的什么東西(比如Gradle)。
repository
可以把Maven的倉庫分為兩種:
- 本地倉庫
- 遠程倉庫
Maven尋找一個dependency時會先從本地倉庫查找,如果找不到則在遠程倉庫查找,發現則下載到本地倉庫使用。
如果都查找失敗,會提示build failure。
或者,我們也可以把本地的jar放到本地倉庫中:
mvn install:install-file -Dfile=jar包的路徑 -DgroupId=我的groupId -DartifactId=我的artifactId -Dversion=我的version -Dpackaging=jar
本地倉庫默認路徑是用戶目錄下的.m2/repository/;
該路徑可以在settings.xml中修改,比如:
<localRepository>
/usr/local/maven/repository
</localRepository>
那么遠程倉庫又在哪里?
打開$M2_HOME/lib/maven-model-builder-3.2.1.jar里的 org/apache/maven/model/pom-4.0.0.xml
看到遠程倉庫的設置如下:
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>http://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
當然,我們也可以配置其他遠程倉庫,比如這樣:
<repositories>
<repository>
<id>opensesame</id>
<name>Alibaba OpenSource Repsoitory</name>
<url>http://code.alibabatech.com/mvn/releases/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
配置遠程倉庫時也需要注意一些選項
- id:倉庫的唯一標識符
- name:倉庫名稱
- url:倉庫地址,通常都是http協議
- layout:默認為default,表示倉庫布局為maven2和maven3的布局
- release/snapshots
- enabled:是否支持發布版/快照版
- updatePolicy:更新策略
- 默認為
daily - always : 每次構建時檢查
- never : 從不檢查
interval : X:每隔X分鍾間差
- 默認為
- checksumPolicy:校驗策略,默認為
warn,另外有fail和ignore
為什么區分release和snapshot? 不能只通過版本號進行區分嗎?
試想一下這樣的場景,假設有A和B兩個模塊,A依賴B,且B尚未開發完成。
如何讓B模塊每次更新后讓A的開發人員獲取?
每次更新后提示A的開發人員從VCS上pull下來構建?
或者不停地換版本號? 確實,就算B有了變化,但version也是依賴的標識之一。
如果依賴是snapshot則能解決這樣的問題,snapshot發布時會加上一個時間戳,每次構建A的時候會檢查B是否最新,間差更新策略就是上面的updatePolicy。
另外,也可以執行mvn clean install-U強制更新。
遠程倉庫不都是想訪問就訪問的,有些倉庫出於安全考慮,需要提供認證信息才可以訪問。
認證必須在settings.xml中設置,下面是一個例子:
<servers>
<server>
<id>server001</id>
<username>my_login</username>
<password>my_password</password>
<privateKey>${user.home}/.ssh/id_dsa</privateKey>
<passphrase>some_passphrase</passphrase>
<filePermissions>664</filePermissions>
<directoryPermissions>775</directoryPermissions>
<configuration></configuration>
</server>
</servers>
