简介

gradle和maven都能够用来构建java程序,甚至在某些状况下,两者还能够相互转换,那么他们两个的共同点和不同点是什么?咱们如何在我的项目中抉择应用哪种技术呢?一起来看看吧。

gradle和maven的比拟

尽管gradle和maven都能够作为java程序的构建工具。然而两者还是有很大的不同之处的。咱们能够从上面几个方面来进行剖析。

可扩展性

Google抉择gradle作为android的构建工具不是没有理由的,其中一个十分重要的起因就是因为gradle够灵便。一方面是因为gradle应用的是groovy或者kotlin语言作为脚本的编写语言,这样极大的进步了脚本的灵活性,然而其本质上的起因是gradle的基础架构可能反对这种灵活性。

你能够应用gradle来构建native的C/C++程序,甚至扩大到任何语言的构建。

相对而言,maven的灵活性就差一些,并且自定义起来也比拟麻烦,然而maven的我的项目比拟容易看懂,并且上手简略。

所以如果你的我的项目没有太多自定义构建需要的话还是举荐应用maven,然而如果有自定义的构建需要,那么还是投入gradle的怀抱吧。

性能比拟

尽管当初大家的机子性能都比拟强劲,如同在做我的项目构建的时候性能的劣势并不是那么的迫切,然而对于大型项目来说,一次构建可能会须要很长的工夫,尤其对于自动化构建和CI的环境来说,当然心愿这个构建是越快越好。

Gradle和Maven都反对并行的我的项目构建和依赖解析。然而gradle的三个特点让gradle能够跑的比maven快上一点:

  • 增量构建

gradle为了晋升构建的效率,提出了增量构建的概念,为了实现增量构建,gradle将每一个task都分成了三局部,别离是input输出,工作自身和output输入。下图是一个典型的java编译的task。

以上图为例,input就是指标jdk的版本,源代码等,output就是编译进去的class文件。

增量构建的原理就是监控input的变动,只有input发送变动了,才从新执行task工作,否则gradle认为能够重用之前的执行后果。

所以在编写gradle的task的时候,须要指定task的输出和输入。

并且要留神只有会对输入后果产生变动的能力被称为输出,如果你定义了对初始后果齐全无关的变量作为输出,则这些变量的变动会导致gradle从新执行task,导致了不必要的性能的损耗。

还要留神不确定执行后果的工作,比如说同样的输出可能会失去不同的输入后果,那么这样的工作将不可能被配置为增量构建工作。

  • 构建缓存

gradle能够重用同样input的输入作为缓存,大家可能会有疑难了,这个缓存和增量编译不是一个意思吗?

在同一个机子上是的,然而缓存能够跨机器共享.如果你是在一个CI服务的话,build cache将会十分有用。因为developer的build能够间接从CI服务器下面拉取构建后果,十分的不便。

  • Gradle守护过程

gradle会开启一个守护过程来和各个build工作进行交互,长处就是不须要每次构建都初始化须要的组件和服务。

同时因为守护过程是一个始终运行的过程,除了能够防止每次JVM启动的开销之外,还能够缓存我的项目构造,文件,task和其余的信息,从而晋升运行速度。

咱们能够运行 gradle --status 来查看正在运行的daemons过程。

从Gradle 3.0之后,daemons是默认开启的,你能够应用 org.gradle.daemon=false 来禁止daemons。

咱们能够通过上面的几个图来直观的感受一下gradle和maven的性能比拟:

  • 应用gradle和maven构建 Apache Commons Lang 3的比拟:

  • 应用gradle和maven构建小我的项目(10个模块,每个模块50个源文件和50个测试文件)的比拟:

  • 应用gradle和maven构建大我的项目(500个模块,每个模块100个源文件和100个测试文件)的比拟:

能够看到gradle性能的晋升是非常明显的。

依赖的区别

gralde和maven都能够本地缓存依赖文件,并且都反对依赖文件的并行下载。

在maven中只能够通过版本号来笼罩一个依赖项。而gradle更加灵便,你能够自定义依赖关系和替换规定,通过这些替换规定,gradle能够构建非常复杂的我的项目。

从maven迁徙到gradle

因为maven呈现的工夫比拟早,所以基本上所有的java我的项目都反对maven,然而并不是所有的我的项目都反对gradle。如果你有须要把maven我的项目迁徙到gradle的想法,那么就一起来看看吧。

依据咱们之前的介绍,大家能够发现gradle和maven从实质上来说就是不同的,gradle通过task的DAG图来组织工作,而maven则是通过attach到phases的goals来执行工作。

尽管两者的构建有很大的不同,然而得益于gradle和maven相识的各种约定规定,从maven移植到gradle并不是那么难。

要想从maven移植到gradle,首先要理解下maven的build生命周期,maven的生命周期蕴含了clean,compile,test,package,verify,install和deploy这几个phase。

咱们须要将maven的生命周期phase转换为gradle的生命周期task。这里须要应用到gradle的Base Plugin,Java Plugin和Maven Publish Plugin。

先看下怎么引入这三个plugin:

plugins {    id 'base'    id 'java'    id 'maven-publish'}

clean会被转换成为clean task,compile会被转换成为classes task,test会被转换成为test task,package会被转换成为assemble task,verify 会被转换成为check task,install会被转换成为 Maven Publish Plugin 中的publishToMavenLocal task,deploy 会被转换成为Maven Publish Plugin 中的publish task。

有了这些task之间的对应关系,咱们就能够尝试进行maven到gradle的转换了。

主动转换

咱们除了能够应用 gradle init 命令来创立一个gradle的架子之外,还能够应用这个命令来将maven我的项目转换成为gradle我的项目,gradle init命令会去读取pom文件,并将其转换成为gradle我的项目。

转换依赖

gradle和maven的依赖都蕴含了group ID, artifact ID 和版本号。两者实质上是一样的,只是模式不同,咱们看一个转换的例子:

<dependencies>    <dependency>        <groupId>log4j</groupId>        <artifactId>log4j</artifactId>        <version>1.2.12</version>    </dependency></dependencies>

上是一个maven的例子,咱们看下gradle的例子怎写:

dependencies {    implementation 'log4j:log4j:1.2.12'  }

能够看到gradle比maven写起来要简略很多。

留神这里的implementation实际上是由 Java Plugin 来实现的。

咱们在maven的依赖中有时候还会用到scope选项,用来示意依赖的范畴,咱们看下这些范畴该如何进行转换:

  • compile:

在gradle能够有两种配置来替换compile,咱们能够应用implementation或者api。

前者在任何应用Java Plugin的gradle中都能够应用,而api只能在应用Java Library Plugin的我的项目中应用。

当然两者是有区别的,如果你是构建应用程序或者webapp,那么举荐应用implementation,如果你是在构建Java libraries,那么举荐应用api。

  • runtime:

能够替换成 runtimeOnly 。

  • test:

gradle中的test分为两种,一种是编译test我的项目的时候须要,那么能够应用testImplementation,一种是运行test我的项目的时候须要,那么能够应用testRuntimeOnly。

  • provided:

能够替换成为compileOnly。

  • import:

在maven中,import常常用在dependencyManagement中,通常用来从一个pom文件中导入依赖项,从而保障我的项目中依赖我的项目版本的一致性。

在gradle中,能够应用 platform() 或者 enforcedPlatform() 来导入pom文件:

dependencies {    implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')     implementation 'com.google.code.gson:gson'     implementation 'dom4j:dom4j'}

比方下面的例子中,咱们导入了spring-boot-dependencies。因为这个pom中曾经定义了依赖项的版本号,所以咱们在前面引入gson的时候就不须要指定版本号了。

platform和enforcedPlatform的区别在于,enforcedPlatform会将导入的pom版本号笼罩其余导入的版本号:

dependencies {    // import a BOM. The versions used in this file will override any other version found in the graph    implementation enforcedPlatform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')    // define dependencies without versions    implementation 'com.google.code.gson:gson'    implementation 'dom4j:dom4j'    // this version will be overridden by the one found in the BOM    implementation 'org.codehaus.groovy:groovy:1.8.6'}

转换repositories仓库

gradle能够兼容应用maven或者lvy的repository。gradle没有默认的仓库地址,所以你必须手动指定一个。

你能够在gradle应用maven的仓库:

repositories {    mavenCentral()}

咱们还能够间接指定maven仓库的地址:

repositories {    maven {        url "http://repo.mycompany.com/maven2"    }}

如果你想应用maven本地的仓库,则能够这样应用:

repositories {    mavenLocal()}

然而mavenLocal是不举荐应用的,为什么呢?

mavenLocal只是maven在本地的一个cache,它蕴含的内容并不残缺。比如说一个本地的maven repository module可能只蕴含了jar包文件,并没有蕴含source或者javadoc文件。那么咱们将不可能在gradle中查看这个module的源代码,因为gradle会首先在maven本地的门路中查找这个module。

并且本地的repository是不可信赖的,因为外面的内容能够轻易被批改,并没有任何的验证机制。

管制依赖的版本

如果同一个我的项目中对同一个模块有不同版本的两个依赖的话,默认状况下Gradle会在解析完DAG之后,抉择版本最高的那个依赖包。

然而这样做并不一定就是正确的, 所以咱们须要自定义依赖版本的性能。

首先就是下面咱们提到的应用platform()和enforcedPlatform() 来导入BOM(packaging类型是POM的)文件。

如果咱们我的项目中依赖了某个module,而这个module又依赖了另外的module,咱们叫做传递依赖。在这种状况下,如果咱们心愿管制传递依赖的版本,比如说将传递依赖的版本升级为一个新的版本,那么能够应用dependency constraints:

dependencies {    implementation 'org.apache.httpcomponents:httpclient'    constraints {        implementation('org.apache.httpcomponents:httpclient:4.5.3') {            because 'previous versions have a bug impacting this application'        }        implementation('commons-codec:commons-codec:1.11') {            because 'version 1.9 pulled from httpclient has bugs affecting this application'        }    }}
留神,dependency constraints只对传递依赖无效,如果下面的例子中commons-codec并不是传递依赖,那么将不会有任何影响。

同时 Dependency constraints须要Gradle Module Metadata的反对,也就是说只有你的module是公布在gradle中才反对这个个性,如果是公布在maven或者ivy中是不反对的。

下面讲的是传递依赖的版本升级。同样是传递依赖,如果本我的项目也须要应用到这个传递依赖的module,然而须要应用到更低的版本(因为默认gradle会应用最新的版本),就须要用到版本降级了。

dependencies {    implementation 'org.apache.httpcomponents:httpclient:4.5.4'    implementation('commons-codec:commons-codec') {        version {            strictly '1.9'        }    }}

咱们能够在implementation中指定特定的version即可。

strictly示意的是强制匹配特定的版本号,除了strictly之外,还有require,示意须要的版本号大于等于给定的版本号。prefer,如果没有指定其余的版本号,那么就应用prefer这个。reject,回绝应用这个版本。

除此之外,你还能够应用Java Platform Plugin来指定特定的platform,从而限度版本号。

最初看一下如何exclude一个依赖:

dependencies {    implementation('commons-beanutils:commons-beanutils:1.9.4') {        exclude group: 'commons-collections', module: 'commons-collections'    }}

多模块我的项目

maven中能够创立多模块我的项目:

<modules>    <module>simple-weather</module>    <module>simple-webapp</module></modules>

咱们能够在gradle中做同样的事件settings.gradle:

rootProject.name = 'simple-multi-module'  include 'simple-weather', 'simple-webapp'  

profile和属性

maven中能够应用profile来区别不同的环境,在gradle中,咱们能够定义好不同的profile文件,而后通过脚本来加载他们:

build.gradle:

if (!hasProperty('buildProfile')) ext.buildProfile = 'default'  apply from: "profile-${buildProfile}.gradle"  task greeting {    doLast {        println message      }}

profile-default.gradle:

ext.message = 'foobar'  

profile-test.gradle:

ext.message = 'testing 1 2 3'

咱们能够这样来运行:

> gradle greetingfoobar> gradle -PbuildProfile=test greetingtesting 1 2 3

资源解决

在maven中有一个process-resources阶段,能够执行resources:resources用来进行resource文件的拷贝操作。

在Gradle中的Java plugin的processResources task也能够做雷同的事件。

比方我能够执行copy工作:

task copyReport(type: Copy) {    from file("$buildDir/reports/my-report.pdf")    into file("$buildDir/toArchive")}

更加简单的拷贝:

task copyPdfReportsForArchiving(type: Copy) {    from "$buildDir/reports"    include "*.pdf"    into "$buildDir/toArchive"}

当然拷贝还有更加简单的利用。这里就不具体解说了。

本文已收录于 http://www.flydean.com/gradle-vs-maven/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!