轉自Lippi-浮生志 :http://ezlippi.com/blog/2015/05/gradle-dependency-management.html
這一章我將介紹Gradle對依賴管理的強大支持,學習依賴分組和定位不同類型倉庫。依賴管理看起來很容易,但是當出現依賴解析沖突時就會很棘手,復雜的依賴關系可能導致構建中依賴一個庫的多個版本。Gradle通過分析依賴樹得到依賴報告,你將很容易找到一個指定的依賴的來源。
Gradle有自己的依賴管理實現,除了支持ant和Maven的特性外,Gradle關心的是性能、可靠性和復用性。
簡要概述依賴管理
幾乎所有基於JVM的項目都會或多或少依賴其他庫,假設你在開發一個基於web的項目,你很可能會依賴很受歡迎的開源框架比如Spring MVC來提高效率。Java的第三方庫一般以JAR文件的形式存在,一般用庫名加版本號來標識。隨着開發的進行依賴的第三方庫增多小的項目變的越來越大,組織和管理你的JAR文件就很關鍵。
不算完美的依賴管理技術
由於Java語言並沒提供依賴管理的工具,所以你的團隊需要自己開發一套存儲和檢索依賴的想法。你可能會采取以下幾種常見的方法:
- 手動復制JAR文件到目標機器,這是最原始的很容易出錯的方法。
- 使用一個共享的存儲介質來存儲JAR文件(比如共享的網盤),你可以加載網絡硬盤或者通過FTP檢索二進制文件。這種方法需要開發者事先建立好與倉庫的連接,手動添加新的依賴到倉庫中。
- 把依賴的JAR文件同源代碼都添加到版本控制系統中。這種方法不需要任何額外的步驟,你的同伴在拷貝倉庫的時候就能檢索依賴的改變。另一方面,這些JAR文件占用了不必要的空間,當你的項目存在相互之間依賴的時候你需要頻繁的check-in的檢查源代碼是否發生了改變。
自動管理依賴的重要性
盡管上面的方法都能用,但是這距離理想的解決方案差遠了,因為他們沒有提供一個標准化的方法來命名和管理JAR文件。至少你得需要開發庫的准確版本和它依賴的庫(傳遞依賴),這個為什么這么重要?
准確知道依賴的版本
如果在項目中你沒有准確聲明依賴的版本這將會是一個噩夢,如果沒有文檔你根本無法知道這個庫支持哪些特性,是否升級一個庫到新的版本就變成了一個猜謎游戲因為你不知道你的當前版本。
管理傳遞依賴
在項目的早期開發階段傳遞依賴就會是一個隱患,這些庫是第一層的依賴需要的,比如一個比較常見的開發方案是將Spring和Hibernate結合起來這會引入超過20個其他的開發庫,一個庫需要很多其他庫來正常工作。下圖展示了Hibernate核心庫的依賴圖:
如果沒有正確的管理依賴,你可以會遇到沒想到過的編譯期錯誤和運行期類加載問題。我們可以總結到我們需要一個更好的方式來管理依賴,一般來講你想在項目元數據中聲明你的依賴和它的版本號。作為一個項目自動化的過程,這個版本的庫會自動從中央倉庫下載、安裝到你的項目中,我們來看幾個現有的開源解決方案。
使用自動化的依賴管理
在Java領域里支持聲明的自動依賴管理的有兩個項目:Apache Ivy(Ant項目用的比較多的依賴管理器)和Maven(在構建框架中包含一個依賴管理器),我不再詳細介紹這兩個的細節而是解釋自動依賴管理的概念和機制。
Ivy和Maven是通過XML描述文件來表達依賴配置,配置包含兩部分:依賴的標識加版本號和中央倉庫的位置(可以是一個HTTP鏈接),依賴管理器根據這個信息自動定位到需要下載的倉庫然后下載到你的機器中。庫可以定義傳遞依賴,依賴管理器足夠聰明分析這個信息然后解析下載傳遞依賴。如果出現了依賴沖突比如上面的Hibernate core的例子,依賴管理器會試着解決。庫一旦被下載就會存儲在本地的緩存中,構建系統先檢查本地緩存中是否存在需要的庫然后再從遠程倉庫中下載。下圖顯示了依賴管理的關鍵元素:
Gradle通過DSL來描述依賴配置,實現了上面描述的架構。
自動依賴管理面臨的挑戰
雖然依賴管理器簡化了手工的操作,但有時也會遇到問題。你會發現你的依賴圖中會依賴同個庫的不同版本,使用日志框架經常會遇到這個問題,依賴管理器基於一個特定的解決方案只選擇其中一個版本來避免版本沖突。如果你想知道某個庫引入了什么版本的傳遞依賴,Gradle提供了一個非常有用的依賴報告來回答這個問題。下一節我會通過一個例子來講解。
聲明依賴
DSL配置block dependencies用來給配置添加一個或多個依賴,你的項目不僅可以添加外部依賴,下面這張表顯示了Gradle支持的各種不同類型的依賴。
這一章直接掃外部模塊依賴和文件依賴,我們來看看Gradle APi是怎么表示依賴的。
理解依賴的API表示
每個Gradle項目都有一個DependencyHandler的實例,你可以通過getDependencies()方法來獲取依賴處理器的引用,上表中每一種依賴類型在依賴處理器中都有一個相對應的方法。每一個依賴都是Dependency的一個實例,group, name, version, 和classifier這幾個屬性用來標識一個依賴,下圖清晰的表示了項目(Project)、依賴處理器(DependencyHandler)和依賴三者之間的關系:
外部模塊依賴
在Gradle的術語里,外部庫通常是以JAR文件的形式存在,稱之為外部模塊依賴,代表項目層次外的一個模塊,這種類型的依賴是通過屬性來唯一的標識,接下來我們來介紹每個屬性的作用。
依賴屬性
當依賴管理器從倉庫中查找依賴時,需要通過屬性的結合來定位,最少需要提供一個name。
- group: 這個屬性用來標識一個組織、公司或者項目,可以用點號分隔,Hibernate的group是org.hibernate。
- name: name屬性唯一的描述了這個依賴,hibernate的核心庫名稱是hibernate-core。
- version: 一個庫可以有很多個版本,通常會包含一個主版本號和次版本號,比如Hibernate核心庫3.6.3-Final。
- classifier: 有時候需要另外一個屬性來進一步的說明,比如說明運行時的環境,Hibernate核心庫沒有提供classifier。
依賴的寫法
你可以使用下面的語法在項目中聲明依賴:
1 |
dependencies { |
你先聲明你要給哪個配置添加依賴,
Java插件指定了若干依賴配置項,其描述如下:當項目的源代碼被編譯時,compile配置項中的依賴是必須的。
- runtime配置項中包含的依賴在運行時是必須的。
- testCompile配置項中包含的依賴在編譯項目的測試代碼時是必須的。
- testRuntime配置項中包含的依賴在運行測試代碼時是必須的。
- archives配置項中包含項目生成的文件(如Jar文件)。
- default配置項中包含運行時必須的依賴。
然后添加依賴列表,你可以用map的形式來注明,你也可以直接用冒號來分隔屬性,比如這樣的:
1 |
//聲明外部屬性 |
如果你項目中依賴比較多,你把一些共同的依賴屬性定義成外部屬性可以簡化build腳本。
Gradle沒有給項目選擇默認的倉庫,當你沒有配置倉庫的時候運行deployTOLocalTomcat任務的時候回出現如下的錯誤:
$ gradle deployToLocalTomcat
:deployToLocalTomcat FAILED
FAILURE: Build failed with an exception.
Where: Build file '/Users/benjamin/gradle-in-action/code/chapter5/cargo-configuration/build.gradle' line: 10
What went wrong:
Execution failed for task ':deployToLocalTomcat'.
> Could not resolve all dependencies for configuration ':cargo'.
> Could not find group:org.codehaus.cargo, module:cargo-core-uberjar, version:1.3.1.
Required by:
:cargo-configuration:unspecified
> Could not find group:org.codehaus.cargo, module:cargo-ant,version:1.3.1.
Required by:
:cargo-configuration:unspecified
到目前為止還沒講到怎么配置不同類型的倉庫,比如你想使用MavenCentral倉庫,添加下面的配置代碼到你的build腳本中:
1 |
repositories { |
檢查依賴報告
當你運行dependencies任務時,這個依賴樹會打印出來,依賴樹顯示了你build腳本聲明的頂級依賴和它們的傳遞依賴:
仔細觀察你會發現有些傳遞依賴標注了*號,表示這個依賴被忽略了,這是因為其他頂級依賴中也依賴了這個傳遞的依賴,Gradle會自動分析下載最合適的依賴。
排除傳遞依賴
Gradle允許你完全控制傳遞依賴,你可以選擇排除全部的傳遞依賴也可以排除指定的依賴,假設你不想使用UberJar傳遞的xml-api的版本而想聲明一個不同版本,你可以使用exclude方法來排除它:
1 |
dependencies { |
exclude屬性值和正常的依賴聲明不太一樣,你只需要聲明group和(或)module,Gradle不允許你只排除指定版本的依賴。
有時候倉庫中找不到項目依賴的傳遞依賴,這會導致構建失敗,Gradle允許你使用transitive屬性來排除所有的傳遞依賴:
1 |
dependencies { |
動態版本聲明
如果你想使用一個依賴的最新版本,你可以使用latest.integration,比如聲明 Cargo Ant tasks的最新版本,你可以這樣寫 org.codehaus .cargo:cargo-ant:latest-integration
,你也可以用一個+號來動態的聲明:
1 |
dependencies { |
Gradle的dependencies任務可以清晰的看到選擇了哪個版本,這里選擇了1.3.1版本:
$ gradle –q dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
Listing 5.4 Excluding a single dependency
Listing 5.5 Excluding all transitive dependencies
Listing 5.6 Declaring a dependency on the latest Cargo 1.x version
Exclusions can be
declared in a shortcut
or map notation.
120 CHAPTER 5 Dependency management
cargo - Classpath for Cargo Ant tasks.
\--- org.codehaus.cargo:cargo-ant:1.+ -> 1.3.1
\--- ...
###文件依賴
如果你沒有使用自動的依賴管理工具,你可能會把外部庫作為源代碼的一部分或者保存在本地文件系統中,當你想把項目遷移到Gradle的時候,你不想去重構,Gradle很簡單就能配置文件依賴。下面這段代碼復制從Maven中央倉庫解析的依賴到libs/cargo目錄。
1 |
task copyDependenciesToLocalDir(type: Copy) { |
運行這個任務之后你就可以在依賴中聲明Cargo庫了,下面這段代碼展示了怎么給cargo配置添加JAR文件依賴:
1 |
dependencies { |
##配置遠程倉庫
Gradle支持下面三種不同類型的倉庫:
下圖是配置不同倉庫對應的Gradle API:
下面以Maven倉庫來介紹,Maven倉庫是Java項目中使用最為廣泛的一個倉庫,庫文件一般是以JAR文件的形式存在,用XML(POM文件)來來描述庫的元數據和它的傳遞依賴。所有的庫文件都存儲在倉庫的指定位置,當你在構建腳本中聲明了依賴時,這些屬性用來找到庫文件在倉庫中的准確位置。group屬性標識了Maven倉庫中的一個子目錄,下圖展示了Cargo依賴屬性是怎么對應到倉庫中的文件的:
RepositoryHandler接口提供了兩個方法來定義Maven倉庫,mavenCentral方法添加一個指向倉庫列表的引用,mavenLocal方法引用你文件系統中的本地Maven倉庫。
添加Maven倉庫
要使用Maven倉庫你只需要調用mavenCentral方法,如下所示:
1 |
repositories { |
添加本地倉庫
本地倉庫默認在 <user_home>/.m2/repository目錄下,只需要添加如下腳本來引用它:
1 |
repositories { |
添加自定義Maven倉庫
如果指定的依賴不存在與Maven倉庫或者你想通過建立自己的企業倉庫來確保可靠性,你可以使用自定義的倉庫。倉庫管理器允許你使用Maven布局來配置一個倉庫,這意味着你要遵守artifact的存儲模式。你也可以添加驗證憑證來提供訪問權限,Gradle的API提供兩種方法配置自定義的倉庫:maven()和mavenRepo()。下面這段代碼添加了一個自定義的倉庫,如果Maven倉庫中不存在相應的庫會從自定義倉庫中查找:
1 |
repositories { |