Gradle 作為 Apache Maven 的有力競争者,在 Java 項目的建構領域逐漸流行起來。很多開源項目,如 Spring 架構、Hibernate、Elasticsearch 和 RxJava 等都使用 Gradle 進行建構。Gradle 也是 Android Studio 中 Android 項目的标準建構方式。越來越多的開發人員開始使用 Gradle 建構自己的 Java 項目。在開始使用 Gradle 時經常會面臨的一個問題是從何處開始。Maven 中可以使用 Archetype 來作為項目的模闆,Gradle 并沒有提供類似的機制。本文要介紹的 Nebula 是由 Netflix 開發的 Gradle 項目建構架構,其目的是為 Gradle 項目提供一個良好的起點,把一些常見的任務添加到建構過程中,進而簡化 Gradle 項目的建構配置。
基本配置
本文通過一個基于 Spring Boot 的 Java Web 示例應用來介紹 Nebula 的使用。Nebula 的核心是一系列由 Netflix 開發和維護的 Gradle 插件。這些插件覆寫 Gradle 項目建構的不同階段,提供不同的功能。代碼清單 1 給出了使用 Nebula 的插件的項目的 Gradle 腳本。Nebula 的插件都釋出到 Gradle 插件倉庫中,是以需要在腳本中添加插件倉庫位址"https://plugins.gradle.org/m2/"。要使用 Nebula 的插件,隻需要在腳本中的 buildscript 中添加對相應插件的依賴,再通過 apply plugin 來應用插件。在應用了插件之後,可以在腳本中進行相應的配置,并通過 Gradle 指令行來運作相關的任務。代碼清單 1 中給出了示例的 Gradle 腳本。
清單 1. 使用 Nebula 的 Gradle 腳本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
下面對 Nebula 提供的常用插件進行具體的介紹。
依賴版本鎖定
在進行項目建構時一個很重要的要求是建構的可重複性。也就是說,代碼倉庫中任意時刻的代碼都應該是可重複建構的。隻有這樣才可以保證代碼的穩定性和品質。不論是最近的代碼,還是幾個星期之前、幾個月之前甚至是幾年之前的代碼,都應該滿足這樣的條件。
可重複建構所面臨的挑戰之一來自于項目所依賴的第三方庫。随着項目的演化,這些第三方庫的版本可能更新。之前的項目版本也許隻能與特定版本的第三方庫協同工作。Gradle 項目直接在 Gradle 檔案中聲明所依賴的第三方庫的版本。除了直接聲明的第三方庫版本之外,有些依賴是通過傳遞關系引入的。這些傳遞依賴的版本是不受應用本身控制的,而由所依賴的庫自己來管理。是以第三方庫自身的依賴的版本更新,也可能造成應用的建構失敗。當項目的傳遞依賴關系很複雜時,很可能會出現傳遞依賴沖突的情況。
Nebula 提供的 nebula.dependency-lock 插件的作用是生成一個包含了全部依賴的具體版本的鎖定檔案。這個檔案由代碼倉庫進行管理。當這個檔案存在時,該插件會確定 Gradle 隻會使用正确版本的依賴。實際上,使用過 Ruby 中的 Gem 管理工具 Bundler 的開發人員會發現,這種版本鎖定功能與 Bundler 生成的 Gemfile.lock 是一樣的。當每次版本釋出時,在建構成功之後,應該通過該插件生成鎖定檔案,并送出到代碼倉庫。
nebula.dependency-lock 插件支援兩類不同的鎖定檔案,分别是項目鎖定檔案和全局鎖定檔案。當 Gradle 項目中包含多個子項目時,每個子項目可以有自己的鎖定檔案。當全局鎖定檔案存在時,子項目中的鎖定檔案不起作用。子項目鎖定檔案的名稱預設為 dependencies.lock,全局鎖定檔案的名稱預設為 global.lock。插件提供的任務如表 1 所示。
表 1. nebula.dependency-lock 插件提供的任務
任務 | 描述 |
---|---|
generateLock / generateGlobalLock | 生成鎖定檔案。generateLock 生成子項目的鎖定檔案, generateGlobalLock 生成全局鎖定檔案。鎖定檔案生成在項目的 build 目錄中。 |
updateLock / updateGlobalLock | 更新子項目/全局鎖定檔案。 |
saveLock / saveGlobalLock | 把生成的鎖定檔案複制到項目目錄中。 |
deleteLock / deleteGlobalLock | 删除子項目/全局鎖定檔案。 |
commitLock | 把鎖定檔案送出到代碼倉庫。 |
該插件提供了一些額外的參數來對任務的行為進行配置。比如,在 updateLock 時可以通過 dependencyLock.updateDependencies 來指定需要更新的依賴的名稱。
依賴版本推薦
在 Gradle 項目中添加第三方依賴時都需要指定版本号。在 Gradle 腳本檔案中直接引用版本号可能造成依賴版本更新時的維護困難。一般的做法是把版本号提取到項目屬性中,進而可以在統一的地方管理所有依賴的版本資訊。當項目較多時,這樣的管理方式也會變得很繁瑣。因為有些通用的庫會在多個項目中使用,而當需要更新這些通用庫的版本時,會需要修改多個項目的 Gradle 檔案。另外一個常見的需求是解決多個依賴庫的版本相容問題。有些第三方庫,如 Spring 架構,包含很多個子項目,當引用這些依賴時,需要確定這些依賴的版本一緻,否則可能出現相容性問題。
這些與依賴的版本号相關的問題,都可以通過 Nebula 提供的依賴推薦插件來解決。在使用了依賴推薦插件之後,沒有聲明版本的第三方依賴的版本号由插件來決定。
依賴推薦插件支援五種方式來聲明所推薦的依賴的版本。第一種方式是通過 Maven BOM 檔案。Maven 的 BOM 檔案中直接定義了依賴的版本資訊。比如,Spring Boot 項目就提供了相應的 BOM 檔案,可以作為建立 Spring Boot 項目的父 POM 檔案。Maven BOM 檔案中通過 dependencyManagement 來指定不同依賴的版本号。代碼清單 2 中給出了作為示例的 Maven BOM 檔案。
清單 2. 示例 Maven BOM 檔案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
當 Maven BOM 檔案釋出到 Maven 倉庫之後,可以作為依賴推薦插件的規則來源,如代碼清單 3 所示。
清單 3. 使用 Maven BOM 檔案指定推薦版本
1 2 3 | |
第二種方式是通過屬性檔案來指定版本。屬性檔案中的鍵是依賴的全名,值是對應的版本号,如代碼清單 4 所示。
清單 4. 使用屬性檔案指定推薦版本
1 2 3 | |
第三種方式是通過由 dependency-lock 插件生成的依賴版本鎖定檔案來指定版本号,如代碼清單 5 所示。該鎖定檔案在生成之後,通常會被送出到代碼倉庫中。項目可以直接使用鎖定檔案來推薦版本号。
清單 5. 使用依賴版本鎖定檔案推薦版本
1 2 3 | |
第四種方式是在 Gradle 檔案中直接使用 java.util.Map 接口對象來提供推薦的版本号,如代碼清單 6 所示。
清單 6. 使用 Map 接口對象推薦版本
1 2 3 4 5 6 | |
最後一種方式是通過完全自定義的代碼來聲明推薦的版本号。在 Gradle 腳本中通過 add 方法來添加規則。add 方法需要根據依賴的組織名和名稱,傳回其對應的推薦版本号。比如可以把推薦的版本号儲存在資料庫之中,然後在 add 方法中進行資料庫查詢并傳回版本号。在代碼清單 7 中,對所有的依賴都傳回推薦的版本号 1.0。
清單 7. 使用 add 方法推薦版本
1 2 3 | |
在使用了版本推薦插件之後,Gradle 對依賴版本的選擇過程發生了變化。優先級最高的是強制應用的依賴版本号,其次是顯式指定了版本号的普通依賴,接着是通過插件推薦的依賴版本号,最後則是由直接依賴引入的傳遞依賴。當傳遞依賴的版本号與插件推薦的版本号發生沖突時,可以應用不同的沖突解決政策。預設的政策是 ConflictResolved,即通過 Gradle 自己的機制來選擇合适的版本号,即優先考慮傳遞依賴中的版本号,再考慮插件所推薦的版本号;另外一種政策是 OverrideTransitives,即選擇插件推薦的版本号,而完全忽略傳遞依賴中的版本号。代碼清單 8 中給出了使用 OverrideTransitives 政策的示例。
清單 8. 使用 OverrideTransitives 政策
1 2 3 4 | |
依賴解析規則
Gradle 本身已經提供了強大的依賴解析功能,可以滿足各種特殊的依賴解析需求。在使用第三方提供的庫時,不可避免的會遇到一些特殊情況,造成正常的依賴解析方式無法滿足需求。這一方面是由于第三方庫本身的原因,如庫可能修改了在 Maven 倉庫中的組織名和名稱,但是并沒有修改其内部的 Java 包名;有的庫可能把所依賴的其他庫打包在自己的 jar 包中。這兩種情況都會造成解析時出現重複名稱的 Java 類。另外一方面是額外的依賴限制。比如某些庫可能隻相容特定版本的其他庫。這樣的版本依賴關系需要顯式聲明。還有一個常見需求是限制庫使用的最低版本。這些需求都可以通過 Gradle 腳本來實作。但是當有多個項目時,這些特殊的依賴解析規則會在不同的 Gradle 腳本中重複,并沒有很好的方式來複用。
Nebula 中的 nebula.resolution-rules 插件提供了一種更好的方式來管理和複用這些依賴解析規則。通過該插件可以把依賴解析規則記錄在 JSON 檔案中,進而可以更好的複用。可以為這些 JSON 檔案建立專門的 Maven 項目并進行版本管理。公司群組織可以管理和維護自己的依賴解析規則。Netflix 自己維護一個公開的代碼倉庫來包含常見庫的依賴解析規則。
在代碼清單 9 中,通過 nebula.resolution-rules 插件的 resolutionRules 聲明了兩種依賴解析規則,第一種來自項目檔案 local-rules.json,第二種來自 Netflix 提供的通用解析規則。
清單 9. 通過 resolutionRules 聲明依賴解析規則
1 2 | |
代碼清單 10 給出了 local-rules.json 檔案的内容,其中通過 deny 規則聲明了不能使用 io.netty:netty-all 依賴,因為該包中有其所使用的其他依賴,很容易造成類名重複。
清單 10. 依賴解析規則示例
1 2 3 4 5 6 7 8 9 10 | |
除了代碼清單 10 中給出的 deny 規則之外,nebula.resolution-rules 插件還支援其他不同的規則:
- replace:當兩個依賴同時出現時,用其中一個替換掉另外一個。
- substitute:類似于 replace,不同的是隻要舊的依賴出現,則替換成新的依賴。
- deny:當出現指定的依賴時,會使得 Gradle 建構失敗。
- reject:指定的依賴不會出現在動态版本的計算過程中。但如果項目顯式的包含這個依賴,則該依賴仍然會被加入。
- align:要求一組依賴的使用相同的版本。
版本釋出
當需要釋出一個項目的新版本時,通常需要執行一系列的動作,包括對代碼倉庫的處理,建構目前版本并釋出到 Maven 倉庫等。nebula-release-plugin 插件的作用是自動化執行這些操作。該插件使用符合語義版本号規則的版本,即 major.minor.patch-<prerelease>+<metadata>的格式。該插件提供了如下的任務:
- snapshot:釋出的版本号為<major>.<minor>.<patch>-SNAPSHOT,如 1.0.0-SNAPSHOT。
- devSnapshot:釋出的版本号為<major>.<minor>.<patch>-dev.#+<hash>,如 0.1.0-dev.1+b8dd0f3。該任務與 snapshot 的差别在于生成的版本号中包含目前 Git commit 的 hash。
- candidate:釋出的版本号為<major>.<minor>.<patch>-rc.#,表示版本釋出的候選,可能存在多個候選,如 1.0.0-rc.1,1.0.0-rc.2 等。該任務會建立與版本号相同的 Git 标簽。
- final:釋出的版本号為<major>.<minor>.<patch>,如 1.0.0。該任務會建立與版本号相同的 Git 标簽。
該插件會在目前的建構成功之後才進行相應的 Git 操作,适合于在持續內建伺服器中運作。
其他插件
除了上述的插件之後,Nebula 還提供了其他有用的插件。
gradle-aggregate-javadocs-plugin 插件用來把多個子項目的 javadoc 合并成單一的文檔。這對于包含多個子項目的項目來說是非常實用的。在添加了該插件之後,可以通過 aggregateJavadocs 任務來生成合并之後的 javadoc。
gradle-override-plugin 插件允許在指令行直接覆寫項目建構中的屬性值。有兩種方式可以覆寫屬性值,一種是以"OVERRIDE_."開頭的環境變量,另外一種是以"override."作為字首的系統屬性。比如在 Gradle 指令行可以通過"-Doverride.sampleProp=value"來覆寫"sampleProp"的值為"value"。
gradle-contacts-plugin 插件允許 Gradle 項目添加開發人員的相關資訊。這些資訊對于開源項目來說尤其重要。通過該插件可以聲明開發人員的基本資訊、聯系方式和角色等。這些資訊會被其他插件所使用,如出現在生成的 jar 包的清單檔案中,所釋出的 Maven 項目的 POM 檔案中。
gradle-metrics-plugin 插件用來收集建構過程中的各種資料并推送到資料存儲中,以友善相關的資料分析。資料可以被推送到 Elasticsearch 或 Splunk 中。
gradle-ospackage-plugin 插件用來生成可以在作業系統上直接運作的包,支援 RedHat 和 Debian。
小結
Gradle 作為流行的建構工具,已經被越來越多的項目所采用。在使用 Gradle 的過程中,會發現有些通用的任務需要在 Gradle 腳本中不斷的重複。Nebula 的意義在于把這些通用的任務整理成單獨的開源插件,使得可以被其他項目所複用。本文對 Nebula 所提供的依賴版本鎖定、依賴版本推薦、依賴解析規則、版本釋出和其他插件進行了詳細的介紹,具體說明了這些插件在實際項目中的用法。在 Gradle 項目中使用這些插件可以極大的減少相關的工作量,并應用來自 Netflix 的最佳實踐。
參考資源 (resources)
- 參考 Nebula 的官方網站,了解 Nebula 的更多内容。
- 檢視 Nebula 的快速入門文檔。
- 了解 Nebula 的版本鎖定插件的更多内容。
- 了解 Nebula 的版本推薦插件的更多内容。
- 了解 Nebula 的依賴解析規則插件的更多内容。
- 了解 Nebula 的版本釋出插件的更多内容。