背景
庖丁同學在混跡在古典互聯網界多年,構建 Java 應用程序一直都是使用Apache Maven,mvn的那幾個常用命令早已爛熟於心,在加上現代 IDE 越來越智能,應用程序的構建從來就不是事兒,但是驚喜總會出現,有一天,庖丁的工作性質發生了改變,眼前的構建工具清一色的都是 Gradle,庖丁一臉的懵逼,束手無策,難道Maven 程序員就不能快速上手 Gradle了嗎?
實戰性或者戰術性程序員這時候做的第一件是事情,肯定是先打開 Gradle 的官方網站,快速學習一遍入門教程,先用起來,再去慢慢理解它的運行機制和設計思想,但是庖丁是解牛的,必須“依乎天理”。深刻理解事物的本質,方能遊刃有餘。
首先回歸問題的本質,無論是 Apache Maven 還是 Gradle ,最核心的功能都是應用程序工程的構建工具,只是實現理念和使用方式上有差異。下面庖丁首先回顧下 Apache Maven 的使用方式和工作機制。
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 文檔的示例片段如下:
<code> <project> 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>
http://maven.apache.org
<dependencies>
<dependency>
<groupid>junit/<groupid>
<artifactid>junit/<artifactid>
<version>4.11/<version>
<scope>test/<scope>
/<dependency>
/<dependencies>
/<project>/<code>
使用 Maven 管理項目構建時,通常第一步去官網下載Apache Maven 的安裝包,配置好環境變量,就可以執行 mvn 命令了,大多數公司會通過 nexus搭建私服,對項目的依賴進行規劃化管理。
我們在使用 Maven 管理項目的構建時,IDE 工具或者腳手架工具都會幫我們生成 pom.xml 文件和基本的項目骨架,然後根據項目的需要進行依賴管理,各種各樣的插件是必不可少的。一切準備就緒後,我們就可以對項目進行構建了,例如編譯,測試,部署等工作。所以Apache Maven 主要幫我們做了兩件大事,一個是依賴版本管理,另一個就是各種構建過程。先看看Apache Maven 的構建過程。
Maven的構建過程
下面是Maven讀取POM文件執行構建過程的示意圖
Maven 的構建過程
總體來說,Maven項目的構建分為五個主要步驟,
- 讀取 POM.xml 文件
- 選擇需要構建的 profile,這個主要方便多構建目標操作,例如單元測試和集成測試,依賴項不一樣
- 下載依賴項目,一般是 Jar 包文件,首先回去讀本地倉庫,然後選擇讀取內部私服,最後是遠程公共倉庫
- 根據構建任務的生命週期,執行構建過程和目標
- 執行插件,部分插件會融合在構建階段中,一起執行。
例如對於編譯這個構建任務,執行如下命令:
<code> mvn compile/<code>
實際上,執行是編譯 compile 這個構建階段,真正執行的是如下階段
- validate
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile
maven 對構建過程的抽象
項目的研發有生命週期,項目的構建過程也一樣,是多個獨立的任務,Maven 對構建構建過程的抽象中,任務是個虛擬的概念, 因為Maven基於構建生命週期的中心概念。這意味著已明確定義了構建和分發特定工件(項目)的過程。對於構建項目的人來說,這意味著僅需學習少量命令即可構建任何Maven項目,並且POM將確保它們獲得所需的結果。這是一種開箱即用的設計,也是一種最佳實踐。
POM 文件
執行Maven命令時,Maven根據POM文件中的配置來執行命令。
依賴項和存儲庫
Pom文件包含了依賴項的配置,依賴項是項目使用的外部JAR文件(Java庫)。如果本地庫中沒有找到依賴項,Maven將從中央庫下載依賴項,並存放在本地庫中。本地存儲庫只是本機上的一個目錄,這個目錄位置可配置。另外除了中央庫,還可以配置其他遠程庫,例如公司內部可以架設一個遠程庫供所有開發人員使用。這裡我們看看 Spring Boot 的依賴項,通過三個維度對依賴包進行管理。
<code>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-web/<artifactid>
<version>2.2.6.RELEASE/<version>
/<dependency>/<code>
構建生命週期、階段和目標
項目的構建通常包含數個相互獨立的任務,可以獨立執行,如生成文檔,構建jar包等。單個任務的執行過程被稱為一個構建生命週期,構建生命週期由一系列的構建階段組成,這些構建生命週期中的每一個都由不同的構建階段列表定義,其中,構建階段代表生命週期中的一個階段。每個階段包含一系列的構建目標。
可以執行構建階段或構建目標。階段按順序執行,執行一個階段則會先執行該階段之前的所有階段。當執行構建階段時,將會按順序執行其中包含的所有構建目標。構建目標可以被分配到一個或多個構建階段。還可以直接執行構建目標。
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 腳本示例。
<code> 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'
}/<code>
挺簡潔,junit 的依賴一行就闡述清楚了
<code> testCompile group: 'junit', name: 'junit', version: '4.13'/<code>
如果使用 Maven 的依賴,配置片段應該是如下這樣,6 行配置代碼,解決了配置文件冗長的 繁瑣,並且提高了可讀性。
<code> <dependency>
<groupid>junit/<groupid>
<artifactid>junit/<artifactid>
<version>4.13/<version>
<scope>test/<scope>
/<dependency>/<code>
mavenCentral(),具備Jar 包存儲倉庫管理和配置,兼容 Maven 倉庫,簡潔的依賴管理和可配置的插件。已經具備了 Maven的基礎功能。Gradle能不能解決 Maven 構建工具的痛點呢?它和Maven 對於項目構建過程的抽象和封裝會一致嗎?
Gradle 的工作機制
Gradle 簡介
Gradle是一個開放源代碼的構建自動化工具,旨在靈活地構建幾乎任何類型的軟件。下圖是 Gradle 官網的截圖,可以看出 Gradle 的雄心,加快研發人員的生產力 。
Gradle是專注於靈活性和性能的開源構建自動化工具。 Gradle構建腳本是使用[Groovy](https://groovy-lang.org/)或[Kotlin](https://kotlinlang.org/)DSL編寫的,主要有如下特質:
- 高度可定製-Gradle以最基本的方式可定製和可擴展的方式建模。
- 快速-Gradle通過重新使用先前執行的輸出,僅處理已更改的輸入並並行執行任務來快速完成任務。**
- 功能強大–Gradle是Android的官方構建工具,並支持許多流行的語言和技術。
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-tool
- https://gradle.org/
閱讀更多 庖丁解架構 的文章