背景
庖丁同學在混跡在古典網際網路界多年,構建 Java 應用程式一直都是使用Apache Maven,mvn的那幾個常用命令早已爛熟於心,在加上現代 IDE 越來越智慧,應用程式的構建從來就不是事兒,但是驚喜總會出現,有一天,庖丁的工作性質發生了改變,眼前的構建工具清一色的都是 Gradle,庖丁一臉的懵逼,束手無策,難道Maven 程式設計師就不能快速上手 Gradle了嗎?
實戰性或者戰術性程式設計師這時候做的第一件是事情,肯定是先開啟 Gradle 的官方網站,快速學習一遍入門教程,先用起來,再去慢慢理解它的執行機制和設計思想,但是庖丁是解牛的,必須“依乎天理”。深刻理解事物的本質,方能遊刃有餘。
首先回歸問題的本質,無論是 Apache Maven 還是 Gradle ,最核心的功能都是應用程式工程的構建工具,只是實現理念和使用方式上有差異。下面庖丁首先回顧下 Apache Maven 的使用方式和工作機制。
Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.
上面這段話來自於 Apache Maven 的官網,Apache Maven是一個軟體專案管理和理解工具。 基於專案物件模型(POM)的概念,Maven可以通過集中的資訊來管理專案的構建,報告和文件。POM 是最基礎也是最核心的元件,這也是 Maven 的配置檔案命名為 pom.xml的主要原因。POM是通過 XML 檔案的格式進行定義。pom.xml 文件的示例片段如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>Maven Quick Start Archetype</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>使用 Maven 管理專案構建時,通常第一步去官網下載Apache Maven 的安裝包,配置好環境變數,就可以執行 mvn 命令了,大多數公司會通過 nexus搭建私服,對專案的依賴進行規劃化管理。
我們在使用 Maven 管理專案的構建時,IDE 工具或者腳手架工具都會幫我們生成 pom.xml 檔案和基本的專案骨架,然後根據專案的需要進行依賴管理,各種各樣的外掛是必不可少的。一切準備就緒後,我們就可以對專案進行構建了,例如編譯,測試,部署等工作。所以Apache Maven 主要幫我們做了兩件大事,一個是依賴版本管理,另一個就是各種構建過程。先看看Apache Maven 的構建過程。
Maven的構建過程下面是Maven讀取POM檔案執行構建過程的示意圖
Maven 構建過程
Maven 的構建過程
總體來說,Maven專案的構建分為五個主要步驟,
讀取 POM.xml 檔案選擇需要構建的 profile,這個主要方便多構建目標操作,例如單元測試和整合測試,依賴項不一樣下載依賴專案,一般是 Jar 包檔案,首先回去讀本地倉庫,然後選擇讀取內部私服,最後是遠端公共倉庫根據構建任務的生命週期,執行構建過程和目標執行外掛,部分外掛會融合在構建階段中,一起執行。例如對於編譯這個構建任務,執行如下命令:
mvn compile
實際上,執行是編譯 compile 這個構建階段,真正執行的是如下階段
validategenerate-sourcesprocess-sourcesgenerate-resourcesprocess-resourcescompilemaven 對構建過程的抽象專案的研發有生命週期,專案的構建過程也一樣,是多個獨立的任務,Maven 對構建構建過程的抽象中,任務是個虛擬的概念, 因為Maven基於構建生命週期的中心概念。這意味著已明確定義了構建和分發特定工件(專案)的過程。對於構建專案的人來說,這意味著僅需學習少量命令即可構建任何Maven專案,並且POM將確保它們獲得所需的結果。這是一種開箱即用的設計,也是一種最佳實踐。
POM 檔案執行Maven命令時,Maven根據POM檔案中的配置來執行命令。
依賴項和儲存庫Pom檔案包含了依賴項的配置,依賴項是專案使用的外部JAR檔案(Java庫)。如果本地庫中沒有找到依賴項,Maven將從中央庫下載依賴項,並存放在本地庫中。本地儲存庫只是本機上的一個目錄,這個目錄位置可配置。另外除了中央庫,還可以配置其他遠端庫,例如公司內部可以架設一個遠端庫供所有開發人員使用。這裡我們看看 Spring Boot 的依賴項,通過三個維度對依賴包進行管理。
可以執行構建階段或構建目標。階段按順序執行,執行一個階段則會先執行該階段之前的所有階段。當執行構建階段時,將會按順序執行其中包含的所有構建目標。構建目標可以被分配到一個或多個構建階段。還可以直接執行構建目標。
Maven有三個內建的構建生命週期:預設(default),清除(clean)和站點(site)。預設生命週期處理專案部署,清理(clean)生命週期處理專案清理,而站點(site)生命週期處理專案的站點文件的建立。
例如,預設生命週期包含以下階段:
驗證(validate)-驗證專案是否正確並且所有必要資訊均可用編譯(compile)-編譯專案的原始碼測試(test)-使用合適的單元測試框架測試已編譯的原始碼。這些測試不應要求將程式碼打包或部署打包(package)-打包已編譯的程式碼,並將其打包為可分發的格式,例如JAR。整合測試(integration-test):處理程式包並將其部署到可以執行整合測試的環境中驗證(verify):執行任何檢查以驗證包裝是否有效並符合品質標準安裝(install):將軟體包安裝到本地儲存庫中,以作為本地其他專案中的依賴項部署(deploy):在整合或釋出環境中完成,將最終程式包複製到遠端儲存庫,以便與其他開發人員和專案共享。外掛Plugin外掛是構建目標的集合,也稱為MOJO (Maven Old Java Object)。可以把外掛理解為一個類,而構建目標是類中的方法。構建階段包含一系列的構建目標,可以理解為按順序呼叫各個外掛中的構建目標(方法),然後一系列的構建階段組成一個構建生命週期。
Maven實際上是一個外掛執行框架。如有必要,可以用java開發自定義外掛。例如我們經常使用的Running MyBatis Generator With Maven
構建Profile如果需要構建專案的不同版本,可以使用構建profile。例如,專案中需要構建開發版本、測試版本以及正式版本,這些版本可以通過在pom檔案中新增不同構建profile構建。執行maven時指定不同的構建profile就可以。
小結Apache Maven是一個軟體專案管理和理解工具。 基於專案物件模型(POM)的概念,通過 XML 進行配置,構建過程基於任務的的生命週期概念。完成了依賴版本管理和專案構建過程。
對 Gradle 工具的猜想既然 Maven 已經使用了挺好了,為什麼會出現新的構建工具之一 Gradle 呢?顯然 Maven 有不足之處或者痛點,想想我們日常工作中的痛點。
pom.xml檔案非常冗長,例如 Apache Flink 的 pom(https://github.com/apache/flink/blob/master/pom.xml) 檔案大概有兩千行,這和 XML 的表達方式有關。構建過程非常慢,特別是大型專案。擴充套件性限制較大,無法和你的程式語言或者指令碼打通如果需要一個新的構建工具,你期望它最好具有哪些特徵呢?
相容 Maven 的依賴管理機制,畢竟你已經有一些專案通過 Maven 管理,當然還有一些非常優秀的第三方 Jar 包,這些對你的專案執行至關重要編譯和構建速度快構建檔案的配置簡單明了,減少冗餘,特別是那些約定俗稱的概念擴張性好,最好能和你使用的 Java 語言打通萬變不離其宗,構建工具的本質工作為依賴管理和專案構建,那麼 Gradle 會不會給你驚喜呢?
Gradle 初見驚訝地發現,熟悉的 pom.xml 檔案不見了,取而代之的是 build.gradle檔案,開啟它發現 XML 格式的內容也不見了,莫慌,肯定是某種程式語言,先瞄一眼,build.gradle 指令碼示例。
plugins { id 'java-library' } repositories { mavenCentral() jcenter() } dependencies { api 'org.apache.commons:commons-math3:3.6.1' implementation 'com.google.guava:guava:28.0-jre' testImplementation 'junit:junit:4.13' }
挺簡潔,junit 的依賴一行就闡述清楚了
testCompile group: 'junit', name: 'junit', version: '4.13'
如果使用 Maven 的依賴,配置片段應該是如下這樣,6 行配置程式碼,解決了配置檔案冗長的 繁瑣,並且提高了可讀性。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency>
mavenCentral(),具備Jar 包儲存倉庫管理和配置,相容 Maven 倉庫,簡潔的依賴管理和可配置的外掛。已經具備了 Maven的基礎功能。Gradle能不能解決 Maven 構建工具的痛點呢?它和Maven 對於專案構建過程的抽象和封裝會一致嗎?
Gradle 的工作機制Gradle 簡介Gradle是一個開放原始碼的構建自動化工具,旨在靈活地構建幾乎任何型別的軟體。下圖是 Gradle 官網的截圖,可以看出 Gradle 的雄心,加快研發人員的生產力 。
圖片來源於 Gradle 官網
Gradle是專注於靈活性和效能的開源構建自動化工具。 Gradle構建指令碼是使用[Groovy](https://groovy-lang.org/)或[Kotlin](https://kotlinlang.org/)DSL編寫的,主要有如下特質:
Gradle 重要核心功能高效能Gradle通過僅執行需要執行的任務來避免不必要的工作,因為它們的輸入或輸出已更改。您還可以使用構建快取來重用以前執行的任務輸出,甚至可以使用其他計算機(具有共享的構建快取)重用任務輸出。
Gradle還實施了許多其他優化措施,並且開發團隊不斷努力以提高Gradle的效能。
JVM基礎Gradle在JVM上執行,並且必須安裝Java開發工具包(JDK)才能使用它。對於熟悉Java平臺的使用者來說,這是一個好處,因為您可以在構建邏輯中使用標準Java API,例如自定義任務型別和外掛。它還使在不同平臺上執行Gradle變得容易。
請注意,Gradle不僅限於構建JVM專案,它甚至附帶對構建本機專案的支援。
約定(Conventions)Gradle從Maven的書中抽出了一片葉子,並通過實現約定使常見型別的專案(例如Java專案)易於構建。應用適當的外掛,您可以輕鬆地為許多專案使用苗條的構建指令碼。但是這些約定並沒有限制您:Gradle允許您覆蓋它們,新增自己的任務以及對基於約定的版本進行許多其他自定義。
可擴充套件性您可以輕鬆擴充套件Gradle以提供您自己的任務型別甚至構建模型。有關此示例,請參見Android構建支援:它添加了許多新的構建概念,例如口味和構建型別。
IDE支援幾個主要的IDE允許您匯入Gradle構建並與其進行互動:Android Studio,IntelliJ IDEA,Eclipse和NetBeans。 Gradle還支援生成將專案載入到Visual Studio所需的解決方案檔案。
洞察力構建掃描提供了有關構建執行的廣泛資訊,可用於識別構建問題。他們特別擅長幫助您發現構建效能方面的問題。您還可以與其他人共享構建掃描,如果您需要諮詢以解決構建問題,這特別有用。
主要差異和對比Gradle和Maven之間的主要區別是靈活性,效能,使用者體驗和依賴性管理。 Maven與Gradle功能比較中提供了這些方面的直觀概述。Gradle 官網中,對 Gradle 和 Maven 從以下幾個方面做了比較詳細的對比:
靈活性Google選擇Gradle作為Android的官方構建工具; 不是因為構建指令碼是程式碼,而是因為Gradle以最基本的方式可擴充套件的方式進行建模。 Gradle的模型還允許將其用於C / C ++的本機開發,並且可以擴充套件為涵蓋任何生態系統。 例如,Gradle在設計時會考慮使用其Tooling API進行嵌入。
Gradle和Maven都提供了配置約定。 但是,Maven提供了一個非常僵化的模型,使定製變得乏味,有時甚至是不可能的。 儘管這可以使您更容易理解任何給定的Maven構建,但是隻要您沒有任何特殊要求,它也就不適合許多自動化問題。 另一方面,Gradle的構建考慮了授權和負責任的使用者。
效能縮短構建時間是提高交付速度的最直接方法之一。 Gradle和Maven都採用某種形式的並行專案構建和並行依賴項解析。 最大的差異是Gradle避免重複工作和增加工作量的機制。 使Gradle比Maven快得多的前3個功能是:
增量性-Gradle通過跟蹤任務的輸入和輸出並僅執行必要的內容,並且僅在可能時處理已更改的檔案,從而避免了工作。Build Cache(構建快取)—重用具有相同輸入的任何其他Gradle構建的構建輸出,包括在機器之間Gradle Daemon —一個長期存在的過程,可將構建資訊“熱”儲存在記憶體中。在Gradle與Maven的效能對比中,這些和更多的效能特性使Gradle在幾乎每種情況下的速度至少是其兩倍(對於使用構建快取的大型構建而言,則要快100倍)。下圖來自 Gradle 官網 。
使用者體驗Maven的任期較長,這意味著它對許多使用者來說都更支援IDE。 但是,Gradle的IDE支援繼續迅速提高。 例如,Gradle現在具有基於Kotlin的DSL,可提供更好的IDE體驗。 Gradle團隊正在與IDE開發商合作,以使編輯支援變得更好-隨時關注更新。
儘管IDE很重要,但是許多使用者還是喜歡通過命令列介面執行構建操作。 Gradle提供了一個現代的CLI,該CLI具有可發現性功能,例如“ gradle task”(漸變任務),以及改進的日誌記錄和命令列完成功能。
最後,Gradle提供了一個基於Web的互動式UI,用於除錯和優化構建:構建掃描。 這些也可以在內部託管,以允許組織收集構建歷史記錄並進行趨勢分析,比較構建以進行除錯或優化構建時間。
依賴管理兩種構建系統都提供了內建功能,可以解決來自可配置儲存庫的依賴關係。兩者都可以在本地快取依賴項並並行下載它們。
作為庫的使用者,Maven允許重寫一個依賴項,但只能按版本。 Gradle提供了可自定義的依賴關係選擇和替換規則,這些規則可以宣告一次,並在專案範圍內處理不需要的依賴關係。這種替換機制使Gradle可以一起構建多個源專案以建立複合構建。
Maven具有很少的內建依賴項作用域,這些作用域在常見的情況下(例如使用測試夾具或程式碼生成)迫使笨拙的模組體系結構。例如,單元測試和整合測試之間沒有分隔。 Gradle允許自定義依賴項範圍,從而提供了更好的建模和更快的構建。
Maven依賴關係衝突解決方案使用最短路徑,這受宣告順序影響。 Gradle可以完全解決衝突,選擇圖中最高版本的依賴項。此外,使用Gradle可以將版本嚴格宣告為允許其優先於傳遞版本的版本,從而可以降級依賴項。
作為庫生產者,Gradle允許生產者宣告“ api”和“實現”依賴項,以防止有害的庫洩漏到使用者的類路徑中。 Maven允許釋出者通過可選的依賴項提供元資料,但僅作為文件提供。 Gradle完全支援功能變體和可選的依賴項。
以上從四個方面對 Gradle 和 Maven 進行了對比,差距確實明顯,但闡述都比較表象,沒有深刻闡述本質的區別。Gradle和Maven對如何構建專案有根本不同的視角。 Gradle提供了一個靈活且可擴充套件的構建模型,該模型將實際工作委託給任務依賴關係圖。 Maven使用固定,線性階段的模型,您可以在其中附加目標(完成工作的事物)。 這可能使兩者之間的遷移看起來令人生畏,但是遷移可能出奇的容易,因為Gradle遵循許多與Maven相同的約定(例如標準專案結構),並且其依賴項管理以類似的方式工作。
所以任務依賴關係圖和線性階段模型才是本質上的差別,也是研發和使用人員認知上的本質差別。
Maven 到 Gradle,認知的轉變Gradle是一種靈活而強大的構建工具,當Maven 程式設計師初次啟動時,很容易感到恐懼。但是,了解以下核心原則將使Gradle更加容易上手,並且您將在不知道該工具的情況下熟練掌握該工具。
Gradle是通用的構建工具Gradle允許您構建任何軟體,因為它對您要構建的內容或應如何完成的工作幾乎沒有任何假設。最明顯的限制是,依賴項管理當前僅支援與Maven和Ivy相容的儲存庫以及檔案系統。
這並不意味著您需要做很多工作來建立構建。 Gradle通過新增一層約定和通過外掛的預構建功能,可以輕鬆構建通用型別的專案(例如Java庫)。您甚至可以建立和釋出自定義外掛來封裝自己的約定並構建功能。
核心模型基於任務Gradle將其構建模型建模為任務(工作單元)的有向無環圖(DAG)。這意味著構建實質上配置了一組任務,並根據它們的依賴關係將它們連線在一起以建立該DAG。建立任務圖後,Gradle將確定需要按順序執行的任務,然後繼續執行它們。
此圖顯示了兩個示例任務圖,一個是抽象圖,另一個是具體圖,其中任務之間的依賴性表示為箭頭:
這樣,幾乎所有構建過程都可以建模為任務圖,這就是Gradle如此靈活的原因之一。任務圖可以由外掛和您自己的構建指令碼定義,任務通過任務依賴機制連結在一起。
任務本身包括:
動作-做某事的工作,例如複製檔案或編譯原始碼輸入-操作使用或對其進行操作的值,檔案和目錄輸出-操作修改或生成的檔案和目錄實際上,以上所有操作都是可選的,具體取決於任務需要執行的操作。某些任務(例如標準生命週期任務)甚至沒有任何動作。他們只是為了方便而將多個任務聚合在一起。
您選擇要執行的任務。通過指定執行所需任務的任務來節省時間,但僅此而已。如果您只想執行單元測試,請選擇執行該任務的任務-通常是測試。如果要打包應用程式,則大多數構建都為此執行組裝任務。最後一件事:Gradle的增量構建支援是可靠且可靠的,因此,除非您確實想執行清理,否則避免清理任務可保持構建快速執行。
Gradle有幾個固定的構建階段重要的是要了解Gradle分三個階段評估和執行構建指令碼:
初始化:設定構建環境,並確定哪些專案將參與其中。配置:構造和配置用於構建的任務圖,然後根據使用者要執行的任務確定需要執行的任務和執行順序。執行: 執行在配置階段結束時選擇的任務。這些階段構成了Gradle的構建生命週期。
與Apache Maven術語的比較Gradle的構建階段與Maven的階段不同。 Maven使用其階段將構建執行分為多個階段。它們的作用類似於Gradle的任務圖,但靈活性較差。
Maven的構建生命週期概念與Gradle的生命週期任務大致相似。
設計良好的構建指令碼主要由宣告性配置而不是命令式邏輯組成。可以理解,在配置階段評估該配置。即便如此,許多此類構建也具有任務操作(例如,通過doLast {}和doFirst {}塊),這些任務在執行階段進行評估。這很重要,因為在配置階段評估的程式碼不會看到在執行階段發生的更改。
配置階段的另一個重要方面是,每次執行構建時都要評估其中涉及的所有內容。因此,最佳做法是在配置階段避免昂貴的工作。構建掃描可以幫助您識別此類熱點。
Gradle的擴充套件方式不止一種如果您僅可以使用Gradle捆綁的構建邏輯來構建專案,那將是很好的選擇,但這幾乎是不可能的。大多數構建都有一些特殊要求,這意味著您需要新增自定義構建邏輯。
Gradle提供了多種機制來擴充套件它,例如:
自定義任務型別。當您希望構建完成現有任務無法完成的工作時,只需編寫自己的任務型別即可。通常,最好將自定義任務型別的原始檔放在buildsrc目錄或打包的外掛中。然後,您可以像Gradle提供的任何任務一樣使用自定義任務型別。
自定義任務動作。您可以通過Task.doFirst()和Task.doLast()方法附加在任務之前或之後執行的自定義構建邏輯。
專案和任務的額外屬性。這些允許您將自己的屬性新增到專案或任務中,然後可以從您自己的自定義操作或任何其他構建邏輯中使用它們。甚至可以將額外的屬性應用於您未明確建立的任務,例如Gradle的核心外掛建立的任務。
自定義約定。約定是簡化構建的強大方法,因此使用者可以更輕鬆地理解和使用它們。從使用標準專案結構和命名約定的構建(例如Java構建)中可以看出這一點。您可以編寫自己的提供約定的外掛-它們只需要為構建的相關方面配置預設值。
自定義模型。Gradle允許您將新概念引入除任務,檔案和依賴項配置之外的內部版本。您可以在大多數語言外掛中看到這一點,這些外掛將源集的概念新增到了構建中。對構建過程進行適當的建模可以大大提高構建的易用性及其效率。
構建指令碼針對API執行將Gradle的構建指令碼視為可執行程式碼很容易,因為它們就是這樣。但這只是一個實現細節:精心設計的構建指令碼描述了構建軟體所需的步驟,而不是這些步驟應如何工作。這是定製任務型別和外掛的工作。
人們普遍誤認為Gradle的功能和靈活性來自其構建指令碼是程式碼這一事實。這離事實還遠。強大的基礎模型和API。正如我們在最佳實踐中建議的那樣,您應該避免在構建指令碼中放置太多(如果有的話)命令式邏輯。
然而,在一個區域中,將構建指令碼視為可執行程式碼很有用:了解構建指令碼的語法如何對映到Gradle的API。由Groovy DSL參考和Javadocs組成的API文件列出了方法和屬性,並引用了閉包和操作。這些在構建指令碼的上下文中是什麼意思?檢視Groovy Build Script Primer,以了解該問題的答案,以便您可以有效地使用API文件。
由於Gradle在JVM上執行,因此構建指令碼也可以使用標準Java API。 Groovy構建指令碼可以另外使用Groovy API,而Kotlin構建指令碼可以使用Kotlin。
總結通過對 Maven 和Gradle 工作機制的介紹,Maven 程式設計師對於如何使用 Gradle 有個認知上的提升,Gradle和Maven對如何構建專案有根本不同的視角。 Gradle提供了一個靈活且可擴充套件的構建模型,該模型將實際工作委託給任務依賴關係圖。 Maven使用固定,線性階段的模型,您可以在其中附加目標(完成工作的事物)。從構架專案這個領域模型來看,有個本質的差別,從而引起架構設計的差異,因此 Maven 程式設計師必須深刻理解這個差異,提升認知,就可以快速上手。而依賴管理,兩個各有千秋,但差異不大。
參考資料Apache Maven 官網:http://maven.apache.org/https://stackoverflow.com/questions/7249871/what-is-a-build-toolhttps://gradle.org/