現在許多人還在為使用 Maven 還是 Gradle 而糾結。如果關注過《Maven 權威指南》作者許曉斌老師在 InfoQ 中發表的文章:《Maven 實戰(六)——Gradle,構建工具的未來?》(http://www.infoq.com/cn/news/2011/04/xxb-maven-6-gradle),那麼一定會有同感:Gradle 太靈活,可能會造成不可控。文章中的原話是:
「Gradle 的另外一個問題就是它太靈活了,雖然它支持約定優於配置,不過從本文你也看到了,破壞約定是多麼容易的事情。人都喜歡自由,愛自定義,覺得自己的需求是多麼的特別,可事實上,從 Maven 的流行來看,幾乎 95% 以上的情況你不需要自行擴展,如果你這麼做了,只會讓構建變得難以理解。從這個角度來看,自由是把雙刃劍,Gradle 給了你足夠的自由,約定優於配置只是它的一個選項而已,這初看起來很誘人,卻也可能使其重蹈 Ant 的覆轍。」
首先看一下我們最初使用 Gradle 構建 Spring Cloud 項目的 build.gradle 的寫法:
buildscript { ext { springCloudVersion = 'Edgware.SR3' springBootVersion = '1.5.9.RELEASE' REPOSITORY_HOME = "http://maven.aliyun.com" } repositories { maven { url "${REPOSITORY_HOME}/nexus/content/groups/public" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }}apply plugin: 'maven'apply plugin: 'java'apply plugin: 'org.springframework.boot'if (JavaVersion.current().isJava8Compatible()) { allprojects { tasks.withType(Javadoc) { options.encoding = 'UTF-8' options.addStringOption('Xdoclint:none', '-quiet') // 關閉 JDK1.8 的 doclint 特性 } }}// 導入 Spring Cloud 依賴dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" }}dependencyManagement { resolutionStrategy { // 檢查遠程依賴是否存在更新 cacheChangingModulesFor 0, 'seconds' cacheChangingModulesFor 0, 'seconds' // 修改本地緩存策略 }}sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8bootRepackage { // 默認只打普通 jar 包 enabled = false}// 打包原始碼,為了方便查看源碼及調試,把源碼也上傳到 nexus 倉庫中task sourcesJar(type: Jar) { classifier = 'sources' from sourceSets.main.allSource}// 打 javadoc 包,為了方便查看注釋,需要把 javadoc 也上傳到 nexus 倉庫中task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir}artifacts { archives jar archives sourcesJar archives javadocJar}uploadArchives { repositories { mavenDeployer { snapshotRepository(url: "${REPOSITORY_HOME}/nexus/content/repositories/snapshots/") { authentication(userName: 'xxx', password: 'xxx') } repository(url: "${REPOSITORY_HOME}/nexus/content/repositories/releases/") { authentication(userName: 'xxx', password: 'xxx') } } }}version = '0.0.1' // 設置版本group = 'com.suixingpay.demo' // 設置 group iddescription = 'demo' // 設置描述dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-web') compileOnly('org.springframework.boot:spring-boot-configuration-processor') compileOnly('org.projectlombok:lombok') testCompile('org.projectlombok:lombok') testCompile "org.springframework.boot:spring-boot-starter-test"}
通過上面代碼我們可以看出以下問題:
雖然 dependencies 部分代碼比 maven 少了許多,但總體來看,代碼並不簡潔;
存在硬編碼帶來的風險,比如要修改 Maven 倉庫的地址,或用戶名及密碼時,需要通知所有人進行變更;
gradle 代碼除 buildscript 代碼塊外,沒有像 maven 中 xml 一樣結構性書寫要求,那麼可能造成每個人的寫法是不一樣的情況;
很難區分每個插件的配置都有哪些,對於不熟悉插件的人來說是非常痛苦的事情,非常不方便維護;
當項目多時,會有非常多的重複代碼,增加了非常多的重複開發及維護成本;比如之前沒有要求將源碼及 javadoc 上傳到 maven 倉庫中,後來因各種原因,增加了這項需求,那麼就需要通知所有把所有代碼都修改一遍;
我們最初的想法是將上面代碼,根據不同的插件,使用不同的 gradle 文件來維護,再將它們放到 GRADLE_HOME/init.d/ 目錄下,但還是更新難的問題。
補充說明 init.gradle 的加載順序,Gradle 會依次對一些目錄進行檢測,按照優先級加載這些目錄下的文件,如果一個目錄下有多個文件被找到,則按照英文字母的順序依次加載。加載優先級如下:
通過 -I 或者 –init-script 參數在構建開始時指定路徑,如
gradle --init-script init.gradle clean gradle --I init.gradle assembleDebug
加載 USER_HOME/.gradle/init.gradle 文件
加載 USER_HOME/.gradle/init.d/ 目錄下的以.gradle 結尾的文件
加載 GRADLE_HOME/init.d/ 目錄下的以.gradle 結尾的文件
後來研究發現 gradle 可以通過 apply from 命令加載外部 gradle 文件,甚至可以加載遠程 http 伺服器中的文件。這個發現讓我們興奮不已,有了它可以幫助我們解決上面所有的問題。
首先將上面 build.gradle 的內容拆分成多個文件:
將 maven 相關的代碼放入 maven.gradle 文件中;
因 java、spring boot 和 Spring Cloud 是我們平時用得最多的插件,所以我們將它們都相關的代碼放到了同一個文件中:spring-cloud.gradle,但 Spring boot 及 Spring Cloud 的版本會因為項目的不同而使用不同的版本,所以需要將它們的單獨提取出來,比如:spring-cloud-dalston-sr4.gradle、spring-cloud-edgware.gradle
然後將上面拆分好的文件入到 git 倉庫中,並修改 build.gradle 文件:
buildscript { // buildscript 不能抽取出來,只能重複寫。 ext{ sxGradleHome = "https://raw.githubusercontent.com/sxfad/gradle-scripts/master/" } apply from: sxGradleHome + 'maven.gradle' apply from: sxGradleHome + 'spring-cloud-edgware.gradle' // 導入使用 Spring Cloud 及相應的 Spring Boot 版本號 repositories { maven { url REPOSITORY_URL } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }}// 參考 https://github.com/sxfad/gradle-scriptsapply from: sxGradleHome + 'maven.gradle'apply from: sxGradleHome + 'spring-cloud.gradle'version = '0.0.1' // 設置版本group = 'com.suixingpay.demo' // 設置 group iddescription = 'demo' // 設置描述dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-web') compileOnly('org.springframework.boot:spring-boot-configuration-processor') compileOnly('org.projectlombok:lombok') testCompile('org.projectlombok:lombok') testCompile "org.springframework.boot:spring-boot-starter-test"}
通過上面的方法處理後,給我們帶來如下好處:
修改後的 build.gradle 變得極其簡潔,只需要關心其基本信息及依賴即可;
對於公司內部使用來說,讓 gradle 的使用更加規範和標準,也使用變得更加簡單;
因為核心 gradle 文件放到了遠程伺服器中,非常方便更新和維護;並且使用 git 管理後,還帶有「版本管理」功能(方便查看文件修改歷史,及回退等);
看到這之後,你一定不會再為選擇 Maven 還是 Gradle 糾結,甚至已經愛上了 Gradle。
作者簡介
邱家榆,隨行付架構師,專注於分布式系統架構及微服務;AutoLoadCache 開源框架作者。
活動推薦
8 月 18 日,InfoQ 將舉辦一場面向技術人的區塊鏈大會!超過二十個區塊鏈落地案例,區塊鏈前沿技術剖析,區塊鏈生態、服務盤點和解讀,盡在 BCCon2018!點擊查看原文進入大會官網了解更多信息。