一、序言
常见的项目构建工具有Ant、Maven、Gradle,以往项目常见采用Maven进构建,但随着技术的发展,越来越多的项目采用Gradle进行构建,例如 Spring-boot。Gradle站在了Ant和Maven构建工具的肩膀上,使用强大的表达式语言Groovy或者Kotlin使其具有易用、灵活的方式自定义构建逻辑,方便扩展,更加适合大型项目构建。
二、性能对比
相比Maven,Gradle为了提高构建的效率,提出了增量构建的概念。Gradle中是以 task 为单位,将一个task分input、任务本身和output。例如下图:input是jdk版本和源文件,output是变异后的class文件。构建的原理就是监听input的变化,当input发生变化的时候,Gradle才会重新构建,否则认为可以复用之前的构建结果。
Gradle可以重用同样的input作为缓存,相比增量编译,缓存则可以跨机器共享,当构建的时候,可以直接从CI服务器拉取构建结果,非常方便。除此之外,Gradle还会开启一个守护进程来处理跟各个build任务的交互,所以不需要每次构建都初始化组件和服务。守护进程默认是开启的,可以通过gradle –status查看运行的守护进程。
这是Gradle和Maven分别构建Apache Commons Lang3耗时的对比,可以看到Gradle的性能提升是很明显的。
二、构建生命周期
Gradle构建的生命周期可以简单划分为初始化、配置和执行,在生命周期各个阶段都提供了用于回调的钩子函数,方便我们监听整个构建过程。
上图是简单的钩子函数示例,依赖钩子函数就可以监听构建的过程
// Setting 项目编译前调用
gradle.beforeProject {
// 在这里写明显无用
println("gradle.beforeProject...")
}
// 所有项目脚本执行完后调用
gradle.buildFinished {
println("gradle.buildFinished ...")
}
除了上面的钩子函数,Gradle也包含其他的钩子函数,比如 settingsEvaluated、projectsEvaluated等,网上资料挺多,这里就不再赘述。
三、依赖管理
Gradle也是依赖Maven的仓库用于Jar包的管理,同样也有本地仓库和中央仓库,也可以配置私服,这一点跟Maven的同样的概念的。举个栗子,下面的代码就指定了对应的本地仓库、中央仓库和私服。
buildscript {
repositories {
mavenLocal()
maven {
credentials {
// 认证信息 配置私服的用户名和密码
}
url = 'https://nexus.xxx.cn/repository/public/'
}
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.3.RELEASE")
}
}
Gradle会按照配置顺序进行依赖包的加载和扫描,依赖包通过dependencies定义,跟Maven类似,同样需要指定包名和版本号来定位。
dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test:2.1.3.RELEASE")
api("org.springframework.boot:spring-boot-starter-amqp:2.1.3.RELEASE")
api 'com.google.cloud:google-cloud-storage:2.4.0'
annotationProcessor "org.projectlombok:lombok:1.18.24"
}
在dependencies中,包含多种类型指定项目依赖项,整体如下:
类型 | 含义 |
---|---|
implementation | 依赖项是会在编译和运行时使用,不会传递给依赖于你的项目的其他模块 |
api | 依赖项是项目的公共 API 依赖项,会在编译、运行和其他依赖于你的项目的模块的编译时使用;如果你的模块是一个库模块,希望这些依赖项对外可见,那么可以使用该关键字 |
compileOnly | 依赖项仅在编译时使用,不会被打包到最终的构建产物中 |
runtimeOnly | 依赖项仅在编译时使用,不会被打包到最终的构建产物中 |
testImplementation | 依赖项仅在测试编译和执行测试时使用,不会传递给项目的主要编译路径 |
testCompileOnly | 类似于 compileOnly,但仅适用于测试编译路径 |
testRuntimeOnly | 类似于 runtimeOnly,但仅适用于测试运行路径 |
annotationProcessor | 依赖项是用于编译时注解处理的依赖项,例如lombok的依赖项 |
四、依赖版本冲突
在项目实际的构建过程中经常依赖包版本冲突的问题,Maven中可以通过 exclude 的方式移除冲突的包,Gradle其实也类似。遇到依赖包冲突,首先是查看依赖报告,可以排除传递性依赖或者强制指定一个版本。
通过exclude排除传递性依赖
dependencies {
implementation('com.example:library') {
exclude group: 'org.unwanted', module: 'unwanted-module'
}
}
使用force强制指定一个版本
dependencies {
resolutionStrategy {
force 'com.example:library:1.0.0'
}
}
如果强制指定了两个相同的包,只是版本不一样,具体选择哪个版本取决于 Gradle 解析依赖的规则,默认情况下会选择最高版本进行解析。
五、多项目构建
在实际开发过程中,通常都是多个模块进行构建,类似Maven提供Parent的方式用来传递模块依赖关系,Gradle也同样提供了多项目构建的方法,用于统一配置公共属性和依赖。
allprojects {
apply plugin: 'java-library'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'maven-publish'
// JVM 版本号要求
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
allprojects 中用来声明所有子模块的通用配置,能在 build.gradle 中配置的语法也都可以同样在allprojects中编写。
另外,Gradle提供 gradle.properties 文件用来统一声明版本号,类似Maven的 properties标签,方便依赖包版本的统一管理。
举个栗子:
springBootVersion=2.1.3.RELEASE
springBootGradlePluginVersion=2.1.3.RELEASE
声明了依赖包SpringBoot和对应Gradle插件的版本,那么依赖配置项则可以修改为如下
dependencies {
testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
api("org.springframework.boot:spring-boot-starter-amqp:${springBootVersion}")
}
六、自动化测试
想要实现Gradle的自动化测试,需要配置测试依赖项,比如这里引入junit4作为测试。
dependencies {
testImplementation 'junit:junit:4.12'
}
编写相应的测试用例,用 @Test 注解来标记测试方法。Gradle提供了内置的测试任务,使用 gradle test 命令可以方便地运行测试用例并生成测试报告,包括测试结果和覆盖率等信息,提供 HTML 可视化图表,测试报告通常位于build/reports/tests
目录下。所以可以将其集成到CI流程中,每次代码提交或构建的时候就能生成相应的报告,还是很直观的。
七、总结
除了上面的内容,Gradle还有很多个性化的用法,比如自定义task等操作来控制构建的流程。因为我之前一直用的都是Maven工具,本身也是Gradle的初学者,最近也是因为新项目而接触Gradle,如果文章有什么错误的地方,也欢迎大家指出,一起学习交流。