Maven实战——Gradle,构建工具的未来?
许晓斌-- 四月 05, 2011
Maven面临的挑战
软件行业新旧交替的速度之快往往令人咂舌,不用多少时间,你就会发现曾经大红大紫的技术已经成为了昨日黄花,当然,Maven也不会例外。虽然目前它基本上是Java构建的事实标准,但我们也能看到新兴的工具在涌现,比如基于Goovy的Gradle,而去年Hibernate宣布从Maven迁移至Gradle这一事件更是吸引了不少眼球。在此之前,我也听到了不少对Maven的抱怨,包括XML的繁冗,不够灵活,学习曲线陡峭等等。那Gradle是否能够在继承 Maven优点的基础上,克服这些缺点呢?带着这个疑问,我开始阅读Gradle的文档并尝试着将一个基于Maven的项目转成用Gradle构建,本文所要讲述大概就是这样的一个体验。需要注意的是,本文完全是基于Maven的角度来看Gradle的,因此对于Ant用户来说,视角肯定会大有不同。
Gradle初体验
Gradle的安装非常方便,下载ZIP包,解压到本地目录,设置 GRADLE_HOME 环境变量并将 GRADLE_HOME/bin 加到 PATH 环境变量中,安装就完成了。用户可以运行gradle -v命令验证安装,这些初始的步骤和Maven没什么两样。Gradle目前的版本是1.0-milestone-1,根据其Wiki上的Roadmap,在1.0正式版发布之前,还至少会有3个里程碑版本,而1.0的发布日期最快也不会早于6月份。而正是这样一个看起来似乎还不怎么成熟的项目,却有着让很多成熟项目都汗颜的文档,其包括了安装指南、基本教程、以及一份近300页的全面用户指南。这对于用户来说是非常友好的,同时也说明了Gradle的开发者对这个项目非常有信心,要知道编写并维护文档可不是件轻松的工作,对于Gradle这样未来仍可能发生很大变动的项目来说尤为如此。
类似于Maven的pom.xml文件,每个Gradle项目都需要有一个对应的build.gradle文件,该文件定义一些任务(task)来完成构建工作,当然,每个任务是可配置的,任务之间也可以依赖,用户亦能配置缺省任务,就像这样:
defaultTasks 'taskB'
t
task taskA << {
println "i'm task A"
}
}
t
task taskB << {
println "i'm task B, and I depend on " + taskA.name
}
}
t
taskB.dependsOn taskA
运行命令$ gradle -q之后(参数q让Gradle不要打印错误之外的日志),就能看到如下的预期输出:
i'm task A
i
i'm task B, and I depend on taskA
这不是和Ant如出一辙么?的确是这样,这种“任务”的概念与用法与Ant及其相似。Ant任务是Gradle世界的第一公民,Gradle对Ant做了很好的集成。除此之外,由于Gradle使用的Grovvy脚本较XML更为灵活,因此,即使我自己不是Ant用户,我也仍然觉得Ant用户会喜欢上Gradle。
依赖管理和集成Maven仓库
我们知道依赖管理、仓库、约定优于配置等概念是Maven的核心内容,抛开其实现是否最优不谈,概念本身没什么问题,并且已经被广泛学习和接受。那Gradle实现了这些优秀概念了么?答案是肯定的。
先看依赖管理,我有一个简单的项目依赖于一些第三方类库包括SpringFramework、JUnit、Kaptcha等等。原来的Maven POM配置大概是这样的(篇幅关系,省略了部分父POM配置):
<properties>
<kaptcha.version>2.3</kaptcha.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.code.kaptcha</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
然后我将其转换成Gradle脚本,结果是惊人的:
dependencies {
compile('org.springframework:spring-core:2.5.6')
compile('org.springframework:spring-beans:2.5.6')
compile('org.springframework:spring-context:2.5.6')
compile('com.google.code.kaptcha:kaptcha:2.3:jdk15')
testCompile('junit:junit:4.7')
}
}
注意配置从原来的28行缩减至7行!这还不算我省略的一些父POM配置。依赖的groupId、artifactId、 version,scope甚至是classfier,一点都不少。较之于Maven或者Ant的XML配置脚本,Gradle使用的Grovvy脚本杀伤力太大了,爱美之心,人皆有之,相比于七旬老妇松松垮垮的皱纹,大家肯定都喜欢少女紧致的脸蛋,XML就是那老妇的皱纹。
关于Gradle的依赖管理起初我有一点担心,就是它是否有传递性依赖的机制呢?经过文档阅读和实际试验后,这个疑虑打消了,Gradle能够解析现有的Maven POM或者Ivy的XML配置,从而得到传递性依赖的信息,并且引入到当前项目中,这实在是一个聪明的做法。在此基础上,它也支持排除传递性依赖或者干脆关闭传递性依赖,其中第二点是Maven所不具备的特性。
自动化依赖管理的基石是仓库,Maven中央仓库已经成为了Java开发者不可或缺的资源,Gradle既然有依赖管理,那必然也得用到仓库,这当然也包括了Maven中央仓库,就像这样:
repositories {
mavenLocal()
mavenCentral()
mavenRepo urls: "http://repository.sonatype.org/content/groups/forge/"
}
}
这段代码几乎不用解释,就是在Gradle中配置使用Maven本地仓库、中央仓库、以及自定义地址仓库。在我实际构建项目的时候,能看到终端打印的下载信息,下载后的文件被存储在USER_HOME/.gradle/cache/ 目录下供项目使用,这种实现的方法与Maven又是及其类似了,可以说Gradle不仅最大限度的继承Maven的很多理念,仓库资源也是直接拿来用。
Gradle项目使用Maven项目生成的资源已经不是个问题了,接着需要反过来考虑,Maven用户是否能够使用 Gradle生成的资源呢?或者更简单点问,Gradle项目生成的构件是否可以发布到Maven仓库中供人使用呢?这一点非常重要,因为如果做不到这一点,你可能就会丢失大量的用户。幸运的是Gradle再次给出了令人满意的答案。使用Gradle的Maven Plugin,用户就可以轻松地将项目构件上传到Maven仓库中:
apply plugin: 'maven'
.
...
u
uploadArchives {
repositories.mavenDeployer {
repository(url: "http://localhost:8088/nexus/content/repositories/snapshots/") {
authentication(userName: "admin", password: "admin123")
pom.groupId = "com.juvenxu"
pom.artifactId = "account-captcha"
}
}
}
}
在上传的过程中,Gradle能够基于build.gradle生成对应的Maven POM文件,用户可以自行配置POM信息,比如这里的groupId和artifactId,而诸如依赖配置这样的内容,Gradle是会自动帮你进行转换的。由于Maven项目之间依赖交互的直接途径就是仓库,而Gradle既能够使用Maven仓库,也能以Maven的格式将自己的内容发布到仓库中,因此从技术角度来说,即使在一个基于Maven的大环境中,局部使用Gradle也几乎不会是一个问题。
约定优于配置
如同Ant一般,Gradle给了用户足够的自由去定义自己的任务,不过同时Gradle也提供了类似Maven的约定由于配置方式,这是通过Gradle的Java Plugin实现的,从文档上看,Gradle是推荐这种方式的。Java Plugin定义了与Maven完全一致的项目布局:
src/main/java
src/main/resources
src/test/java
src/test/resources
区别在于,使用Groovy自定义项目布局更加的方便:
sourceSets {
main {
java {
srcDir 'src/java'
}
resources {
srcDir 'src/resources'
}
}
}
}
Gradle Java Plugin也定义了构建生命周期,包括编译主代码、处理资源、编译测试代码、执行测试、上传归档等等任务:
Figure 1. Gradle的构建生命周期
相对于Maven完全线性的生命周期,Gradle的构建生命周期略微复杂,不过也更为灵活,例如jar这个任务是用来打包的,它不像Maven那样依赖于执行测试的test任务,类似的,从图中可以看到,一个最终的build任务也没有依赖于uploadArchives任务。这个生命周期并没有将用户限制得很死,举个例子,我希望每次build都发布 SNAPSHOT版本到Maven仓库中,而且我只想使用最简单的$ gradle clean build命令,那只需要添加一行任务依赖配置即可:
build.dependsOn 'uploadArchives'
由于Gradle完全是基于灵活的任务模型,因此很多事情包括覆盖现有任务,跳过任务都非常易于实现。而这些事情,在Maven的世界中,实现起来就比较的麻烦,或者说Maven压根就不希望用户这么做。
小结
一番体验下来,Gradle给我最大的感觉是两点。其一是简洁,基于Groovy的紧凑脚本实在让人爱不释手,在表述意图方面也没有什么不清晰的地方。其二是灵活,各种在Maven中难以下手的事情,在Gradle就是小菜一碟,比如修改现有的构建生命周期,几行配置就完成了,同样的事情,在Maven中你必须编写一个插件,那对于一个刚入门的用户来说,没个一两天几乎是不可能完成的任务。
不过即使如此,Gradle在未来能否取代Maven,在我看来也还是个未知数。它的一大障碍就是Grovvy,几乎所有 Java开发者都熟悉XML,可又有几个人了解Groovy呢?学习成本这道坎是很难跨越的,很多人抵制Maven就是因为学起来不容易,你现在让因为一个构建工具学习一门新语言(即使这门语言和Java非常接近),那得到冷淡的回复几乎是必然的事情。Gradle的另外一个问题就是它太灵活了,虽然它支持约定优于配置,不过从本文你也看到了,破坏约定是多么容易的事情。人都喜欢自由,爱自定义,觉得自己的需求是多么的特别,可事实上,从Maven的流行来看,几乎95%以上的情况你不需要自行扩展,如果你这么做了,只会让构建变得难以理解。从这个角度来看,自由是把双刃剑,Gradle给了你足够的自由,约定优于配置只是它的一个选项而已,这初看起来很诱人,却也可能使其重蹈Ant的覆辙。Maven在Ant的基础上引入了依赖管理、仓库以及约定优于配置等概念,是一个很大的进步,不过在我现在看来,Gradle并没有引入新的概念,给我感觉它是一个结合Ant和Maven理念的优秀实现。
如果你了解Groovy,也理解Maven的约定优于配置,那试试Gradle倒也不错,尤其是它几乎能和现有的Maven系统无缝集成,而且你也能享受到简洁带来的极大乐趣。其实说到简洁,也许在不久的将来Maven用户也能直接享受到,Polyglot Maven在这方面已经做了不少工作。本文完全基于Maven的视角介绍Gradle这一构建工具的新秀,不过限于篇幅原因,无法深入Gradle的方方面面,例如Gradle也支持多模块构建,它提供了GUI操作界面,支持Grovvy(理所当然)和Scala项目等等。有兴趣的读者可以自行进一步了解。
*******************************************************
2009-03-27
Java构建工具的未来
不久之前博客Adam Bien写了关于一篇关于老话题:Ant或者Maven的文章,文中有写道“Maven的真正厉害之处是它的依赖管理(dependency management),要做许多工作才能让Ant的依赖管理达到Maven的程度……”文章对Maven似乎有所偏爱,这引起来了Adam Pohorecki的争论,并由此进一步谈到了Java构建工具的未来:
尽管Ant没有内置的依赖管理是个事实,但是将Ivy整合到你build.xml中还是很简单的,所以我认为Adam Bien所说的“许多工作”是个误导。
如果要我在Ant和Maven之间选择,我会选择前者。我也理解为什么很多人喜欢将Maven作为他们首选的构建工具,因为它有构建脚本,只需要几行代码来配置就可以有许多功能:依赖管理、内置的编译和打包应用的任务、与Jetty的集成、干净的项目web网址,与cobertura的集成、pmd或者findbugs。在这种情况下,如果你启动的项目非常常规(使用Spring & Hibernate的应用),Maven就是非常好的选择。
没有人想写构建脚本,因为这种代码没有任何真正的商业价值。如果没有遇到问题,你会觉得Maven真的很棒。但也许你的项目不是常规的,也许你使用的插件彼此冲突,在Maven这样的构建系统中有许多失败的地方,而且经常你很难找到它们的来源并修复它们。
Ant比Maven更值得选择的优点并不明显。而且Ant用手来编写所有的构建代码确实很遭罪。但一旦搞定之后,构建脚本就真正成了你的代码。而且在运行出现问题时,你可以检查代码并修改之,不必像无头苍蝇一样无迹可寻。
但是,Maven和Ant就只是我们的选择么?是否还有比它们更好的选择?在过去的几年中,我们看到很多项目,他们使用的工具不再使用XML来定义构建逻辑,而是真正的编程语言像Groovy、Ruby、Python,它们经常允许依赖管理。这里有几个:GRADLE、Gant、Kundo、 Raven、 Buildr。
我最爱的构建工具是Gant,它至今也不过发布了几个月而已。Gant基本是一种Groovy的方式调用了Ant任务,比较起老旧的build.xml它有很多优点。通过使用真正的编程语言而不是XML,许多本来在XML中很是枯燥的任务变得简单:将通用代码提取为方法、循环、条件逻辑。实际上,XML并非编程语言也不应当被当成编程语言来使用。使用像Groovy或者Ruby这样的语言来构建脚本更加简洁、易懂,而且杂乱冗余的代码更少,结构上也更清晰。
在我看来,在Java环境构建自动化的未来是Gradle,或者是另外一些有相似特性的工具。Gradle是一种我所喜欢的构建脚本:如果是简单、标准的项目你只需要几行配置就可以搞定。它甚至在不使用Maven或者Ivy套件库的情况下允许可传递的依赖管理(transitive dependency management)。当我们第一次知道“依赖管理”,是用ivy.xml或者pom.xml写下我们的依赖,然后不得不安装一个独立的仓库(repository )来存储这些不能在Maven库中找到的依赖,不得不修补pom.xml文件,因为有些没有声明它们的依赖。现在这一切可以成为历史了,我们有DM,只要在SVN库中存储我们需要的libs,然后在用到的时候转到真正的DM就可以了。
而且,如果是大的项目,除了其他特性之外Gradle拥有对多项目构建的第一类(first-class)支持,(比如你可以规定项目依赖,当你构建一个附属项目时,它的依赖也会被构建好)。Gradle还有其他很多特性,你可以在官网的用户指南上寻找,大概100多页。我的意思是如果你肯花点时间来阅读,你就会发现Gradle将会让你的开发变得多么地容易。
但我还是不得不承认,Gradle也许不适合“产品理念”,毕竟项目开发还是个新事物。但如果你想找个更好的工具来替代Ant,Gradle就是。如果你想找工具代替Maven,也许还得等几个月,因为Gradle目前还没有支持许多Maven用户所依赖的特性,比如项目网址、为主要的IDEs生成项目文档。
概括来说,虽然对自动化构建而言没有一种方法解决所有问题的解决方案,但正在进步的新工具却在努力做到这点。既可以按照惯例来构建,也可以按照你自己想要的方法来构建,Gradle在这两方面既吸引了Ant也吸引了Maven的fans。在我看来,未来构建脚本是用像Groovy或者Ruby这样的编程语言来编写的,而Gradle将在工具中占据重中之重。