简介
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 greeting
foobar
> gradle -PbuildProfile=test greeting
testing 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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!