Maven和Gradle對比
- 2020 年 2 月 21 日
- 筆記
Java世界中主要有三大構建工具:Ant、Maven和Gradle。經過幾年的發展,Ant幾乎銷聲匿跡、Maven也日薄西山,而Gradle的發展則如日中天。筆者有幸見證了Maven的沒落和Gradle的興起。Maven的主要功能主要分為5點,分別是依賴管理系統、多模組構建、一致的項目結構、一致的構建模型和插件機制。我們可以從這五個方面來分析一下Gradle比起Maven的先進之處。
依賴管理系統
Maven為Java世界引入了一個新的依賴管理系統。在Java世界中,可以用groupId、artifactId、version組成的Coordination(坐標)唯一標識一個依賴。任何基於Maven構建的項目自身也必須定義這三項屬性,生成的包可以是Jar包,也可以是war包或者ear包。一個典型的依賴引用如下所示:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> </dependency>
從上面可以看出當引用一個依賴時,version可以省略掉,這樣在獲取依賴時會選擇最新的版本。而存儲這些組件的倉庫有遠程倉庫和本地倉庫之分。遠程倉庫可以使用世界公用的central倉庫,也可以使用Apache Nexus自建私有倉庫;本地倉庫則在本地電腦上。通過Maven安裝目錄下的settings.xml文件可以配置本地倉庫的路徑,以及採用的遠程倉庫的地址。
Gradle在設計的時候基本沿用了Maven的這套依賴管理體系。不過它在引用依賴時還是進行了一些改進。首先引用依賴方面變得非常簡潔。
dependencies { compile 'org.hibernate:hibernate-core:3.6.7.Final' testCompile 『junit:junit:4.+' }
第二,Maven和Gradle對依賴項的scope有所不同。在Maven世界中,一個依賴項有6種scope,分別是complie(默認)、provided、runtime、test、system、import。而grade將其簡化為了4種,compile、runtime、testCompile、testRuntime。那麼如果想在gradle使用類似於provided的scope怎麼辦?別著急,由於gradle語言的強大表現力,我們可以輕鬆編寫程式碼來實現類似於provided scope的概念(例如How to use provided scope for jar file in Gradle build?)。
第三點是Gradle支援動態的版本依賴。在版本號後面使用+號的方式可以實現動態的版本管理。
第四點是在解決依賴衝突方面Gradle的實現機制更加明確。使用Maven和Gradle進行依賴管理時都採用的是傳遞性依賴;而如果多個依賴項指向同一個依賴項的不同版本時就會引起依賴衝突。而Maven處理這種依賴關係往往是噩夢一般的存在。而Gradle在解決依賴衝突方面相對來說比較明確。在Chapter 23. Dependency Management
中的23.2.3章節詳細解讀了gradle是如何處理版本衝突的。
多模組構建
在SOA和微服務的浪潮下,將一個項目分解為多個模組已經是很通用的一種方式。在Maven中需要定義個parent POM作為一組module的聚合POM。在該POM中可以使用標籤來定義一組子模組。parent POM不會有什麼實際構建產出。而parent POM中的build配置以及依賴配置都會自動繼承給子module。
而Gradle也支援多模組構建。而在parent的build.gradle中可以使用allprojects和subprojects程式碼塊來分別定義裡面的配置是應用於所有項目還是子項目。對於子模組的定義是放置在setttings.gradle文件中的。
在gradle的設計當中,每個模組都是Project的對象實例。而在parent build.gradle中通過allprojects或subprojects可以對這些對象進行各種操作。這無疑比Maven要靈活的多。
比如在parent的build.gradle中有以下程式碼:
allprojects { task hello << { task -> println "I'm $task.project.name" } }
執行命令gradle -q hello會依次列印出父module以及各個submodule的項目名稱。這種強大的能力能讓gradle對各個模組具有更強的訂製化。
一致的項目結構
在Ant時代大家創建Java項目目錄時比較隨意,然後通過Ant配置指定哪些屬於source,那些屬於testSource等。而Maven在設計之初的理念就是Conversion over configuration(約定大於配置)。其制定了一套項目目錄結構作為標準的Java項目結構。一個典型的Maven項目結構如下:

Gradle也沿用了這一標準的目錄結構。如果你在Gradle項目中使用了標準的Maven項目結構的話,那麼在Gradle中也無需進行多餘的配置,只需在文件中包含apply plugin:』java』,系統會自動識別source、resource、test srouce、 test resource等相應資源。
不過Gradle作為JVM上的構建工具,也同時支援groovy、scala等源程式碼的構建,甚至支援Java、groovy、scala語言的混合構建。雖然Maven通過一些插件(比如maven-scala-plugin)也能達到相同目的,但配置方面顯然Gradle要更優雅一些。
一致的構建模型
為了解決Ant中對項目構建活動缺乏標準化的問題,Maven特意設置了標準的項目構建周期,其默認的構建周期如下所示:
<phases> <phase>validate</phase> <phase>initialize</phase> <phase>generate-sources</phase> <phase>process-sources</phase> <phase>generate-resources</phase> <phase>process-resources</phase> <phase>compile</phase> <phase>process-classes</phase> <phase>generate-test-sources</phase> <phase>process-test-sources</phase> <phase>generate-test-resources</phase> <phase>process-test-resources</phase> <phase>test-compile</phase> <phase>process-test-classes</phase> <phase>test</phase> <phase>prepare-package</phase> <phase>package</phase> <phase>pre-integration-test</phase> <phase>integration-test</phase> <phase>post-integration-test</phase> <phase>verify</phase> <phase>install</phase> <phase>deploy</phase> </phases>
而這種構建周期也是Maven最為人詬病的地方。因為Maven將項目的構建周期限制的太死,你無法在構建周期中添加新的phase,只能將插件綁定到已有的phase上。而現在項目的構建過程變得越來越複雜,而且多樣化,顯然Maven對這種複雜度缺少足夠的應變能力。
比如你想在項目構建過程中進行一項壓縮所有javascript的任務,那麼就要綁定到Maven的現有的某個phase上,而顯然貌似放在哪個phase都不太合適。而且這些phase都是串列的,整個執行下來是一條線,這也限制了Maven的構建效率。而Gradle在構建模型上則非常靈活。
在Gradle世界裡可以輕鬆創建一個task,並隨時通過depends語法建立與已有task的依賴關係。甚至對於Java項目的構建來說,Gradle是通過名為java的插件來包含了一個對Java項目的構建周期,這等於Gradle本身直接與項目構建周期是解耦的。
插件機制
Maven和Gradle設計時都採用了插件機制。但顯然Gradle更勝一籌。主要原因在於Maven是基於XML進行配置。所以其配置語法太受限於XML。即使實現很小的功能都需要設計一個插件,建立其與XML配置的關聯。比如想在Maven中執行一條shell命令,其配置如下:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2</version> <executions> <execution> <id>drop DB => db_name</id> <phase>pre-integration-test</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>curl</executable> <arguments> <argument>-s</argument> <argument>-S</argument> <argument>-X</argument> <argument>DELETE</argument> <argument>http://${db.server}:${db.port}/db_name</argument> </arguments> </configuration> </execution> </executions> </plugin>
而在Gradle中則一切變得非常簡單。
task dropDB(type: Exec) { commandLine 『curl』,』-s』,』s』,』-x』,』DELETE』,"http://${db.server}:{db.port}/db_name" }
在創建自定義插件方面,Maven和Gradle的機制都差不多,都是繼承自插件基類,然後實現要求的方法。這裡就不展開說明。
從以上五個方面可以看出Maven和Gradle的主要差異。Maven的設計核心Convention Over Configuration被Gradle更加發揚光大,而Gradle的配置即程式碼又超越了Maven。在Gradle中任何配置都可以作為程式碼被執行的,我們也可以隨時使用已有的Ant腳本(Ant task是Gradle中的一等公民)、Java類庫、Groovy類庫來輔助完成構建任務的編寫。
這種採用本身語言實現的DSL對本身語言項目進行構建管理的例子比比皆是。比如Rake和Ruby、Grunt和JavaScript、Sbt和Ruby…..而Gradle之所以使用Groovy語言實現,是因為Groovy比Java語言更具表現力,其語法特性更豐富,又兼具函數式的特點。這幾年興起的語言(比如Scala、Go、Swift)都屬於強類型的語言,兼具面向對象和函數式的特點。
最後想說的Gradle的命令行比Maven的要強大的多。
來源:https://www.cnblogs.com/lykbk/p/erwerwerwerwerwerwe.html
來都來了,走啥走,留個言唄~
IT大咖說 | 關於版權
由「IT大咖說(ID:itdakashuo)」原創的文章,轉載時請註明作者、出處及微信公眾號。