优点:
- 构建工具进化出内在逻辑
- 不僵化,灵活,构建逻辑可以直接放在构建脚本中。
缺点:
- 学习成本高,为了一个构建,学习一门新语言groovy,还要学习gradle的DSL
- 因为是一门语言,而不仅仅是一个构建工具,导致易用性骤降,比maven掉几百条街
- IDE的插件支持差,语法提示差,甚至闭包内的语法提示全无
- 构建慢,构建脚本间的任务过于松散,怎么串联到一起的都不知道
- 仅仅是为了打包,可gradle的打包过于费劲
- 构建任务是一个不需要经常改动的过程,gradle花了大量的心血让你在改动的时候可以简单通过脚本实现,但是为了这1%可能的变动,需要花掉99%的经历学习融汇贯通gradle以应对这1%的变动,已经是得不偿失,而且在构建脚本中添加逻辑的行为已经是本末倒置了。
对比maven,maven的优点:
- Xml严谨,不容易出错
- 插件逻辑和配置分离,清晰,不像gradle可以把构建逻辑放在构建脚本中。
- IDE插件支持完善, 可以通过插件完成递归性依赖的排查,语法提示完善。
一. 构建JAVA项目
1.1 gradle项目结构
gradle项目结构跟maven的类似,gradle项目需要有一个build.gradle文件。
1.2 使用JAVA插件
1
apply plugin: "java"
使用这个命令便是使用java插件,java插件是gradle官方的自带的插件之一, 这个插件会对java源码进行编译,测试,打包等。
那这个插件如何知道源码的位置的呢?这个是有这个JAVA插件引入的约定之一,插件会到src/main/java目录下进行查找。
1
2
3
4
5
6
7
8
9
10
11
12
> gradle build //java插件提供的一个任务build,这个build任务会以正确的顺序编译,测试和打包源码
:compileJava //每一行就代表这个java插件的一个任务的执行
:processResources
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE //意味着这个任务被跳过了,gradle的增量式构建自动跳过不需要被运行的任务
:processTestResources
:testClasses
:test
:check
:build
执行完这个java插件的build任务以后,将会在项目的根目录中生成一个build的目录,这个目录与maven中的target目录相似。 里面包含了编译出来的class文件, jar文件
1.3 gradle 命令行
上面说了,在build.gradle中使用到了java插件, 执行命令的时候实际是执行这个插件中的一个task。如何知道有什么task可以执行呢?
常用命令
命令 | 解释 |
---|---|
gradle -q tasks | 查看build.gradle脚本中可执行的tasks有哪些 |
gradle properties | 查看build.gradle脚本中所有的可以设置的属性有哪些,包括插件中定义的属性 和gradle定义的内置属性 |
1.4 定制项目
java插件引入以后,源码结构(src/main/java/),编译打包(build/)都是使用的默认的约定。 gradle支持通过在build.gradle中通过DSL语法进行自定义这部分的约定的选项和配置。
build.gradle 实际就是一个groovy脚本,groovy是一门语言,groovy脚本实际就是一个源码文件, 所有的源码文件都是由属性变量和方法组成。 gradle脚本中引入了插件以后, 除了可以使用这个插件的方法意外, 还可以修改相关的属性。
gradle的脚本中属性的来源主要有引入的插件中的属性和来自gradle内置的属性。除了看代码外还有什么方法能知道你当前能定义的属性有哪些呢, gradle提供了一个命令
gradle properties
来查看当前的脚本中有哪些属性是可以设置的。
Gradle DSL规范可以在 Gradle DSL reference 中进行参考. 这里面可以看到能在gradle脚本中修改的属性有哪些
1.4.1 修改项目属性
1
2
3
4
5
6
7
8
9
apply plugin: "java"
version=0.1
sourceCompatibility=1.6
jar { //具体可以看上面的reference
manifest { //设置名字为jar的task的方法 manifest(Closur clusor) 传参为闭包
attributes "Main-Class":"com.my.hello.todo" //这里的attributes只能通过看源码进行查看
}
}
1.4.2 配置外部依赖
要配置外部依赖主要用到两个配置元素:repositories和dependencies。在java项目中, Gradle要求你至少使用一个集中仓库,这样后面定义的类就会优先到仓库里面查找, 找到的就会复制到自己的本地cache目录下面, 没找到就会到远端下载到自己的cache目录下面。
1
2
3
4
5
6
7
8
9
reposities {
mavenCentral()
}
dependencies {
compile group: "org.apache.common", name: "commons-lang3", version: "3.1"
providerCompile: "javax.servlet:servlet-api:3.1"
runtime: "javax.servlet:jstl:1.1.2"
}
1.5 Gradle包装器
gradle wrapper的意义在于保持gradle的版本,做到在每台机器上build的结果一致的效果。
- build.gradle中创建生成wrapper的任务
- 执行wrapper任务,生成gradlew和gradlew.bat脚本
1
2
3
4
5
6
7
8
//build.gradle
task wrapper(type: Wrapper) {
gradleVersion: 1.7
}
//执行build.gradle中的wrapper任务
gradle wrapper
//生成gradlew或gradlew.bat命令
二. 构建脚本
2.1 构建块
每个gradle项目的build.gradle脚本都包含3个基本的构建块, project、task和property。
- 一个gradle至少有一个project, 多个project中必须有依赖关系。
- 一个project中有多个task, task之间也可以有依赖关系。
- project和task中的暴露属性都可以影响构建。
2.1.1 project
gradle中的buiild.gradle类似于maven中的 pom.xml。
1
//TODO 画下面的UML图
2.1.2 task
task的重要任务功能包括: 任务动作和任务依赖。
1
//TODO 画下面的UML图
2.1.3 property
每个Task和Project都提供了可以通过getter和setter访问的属性。
属性主要包括以下几种:
- gradle属性:项目根目录/gradle.properties或者<USER HOME>/.gradle/gradle.properties下面,在用户目录下的属性文件只能有一个,且级别比项目根目录的要高
- 自定义属性:在项目的buiild.gradle文件中定义
- 项目属性:通过-P命令行传入
- 系统属性:通过-D命令行传入
- 环境属性
2.2 使用task
默认情况下, 新创建的task都是org.gradle.api.DefaultTask类型的,标准的org.gradle.apii.Task实现。
2.2.1 给task添加动作
gradle的project接口给了两个地方声明task动作,doFirst(Closure)和doLast(Closure)
doLast动作也可以通过 « 进行声明。
1
2
3
4
5
6
7
8
9
task printVersion {
doFirst {
println "do first"
}
doLast {
println "do last"
}
}
2.2.2 访问task属性
直接在task定义的属性中进行访问或者在动作中进行访问task。
2.2.3 定义task依赖
1
2
3
4
5
task first << {println "first"}
task second << {println "second"}
task printVersion(dependsOn: [second, first]) << {logger.quiet "version $version"}
task third << {println "third"}
third.dependsOn("printVersion")
// 最后执行链路为:first -> second -> printVersion -> third
2.2.4 终结task
2.2.5 Task添加任意代码
java中的bean叫做POJO, groovy中的bean叫做POGO, 同时groovy提供了语法糖
- 属性不需要添加控制权限符
- 属性的getter和setter方法自动由groovy生成。
2.2.5.1 添加配置块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ext.versionFile=file("version.properties")
task loadVersion {
project.version=readVersion()
}
ProjectVersion readVersion(){
logger.quiet "reading the version.properties file"
if(!versionFile.exists()){
throw new GradleException("version.properties is not exist")
}
Properties pros = new Properties()
versionFile.withInputStream(stream -> pros.load(stream))
new ProjectVersion(Integer.valueOf(pros.major), Integer.valueOf(pros.minor), Boolean.valueOf(pros.release))
}
执行gradle loadVersion, 可以看到这个readVersion的方法被完全执行, 上面的这个task只定义了属性, 并没有定义动作(doFirst, doLast这些),但是这个task的属性部分依然执行了。
2.2.5.2 gradle构建的生命周期
上面只定义了task的属性部分, 没有定义task的动作,但是task的属性部分依然完全执行了, 原因就是在gradle的生命周期中,有三个阶段, 初始化,配置阶段,执行阶段。
2.2.6 task的input和output
2.2.7 编写自定义task
2.2.8 使用自定义task
2.2.9 内置task类型
2.2.9.1 使用task类型
2.2.10 task依赖推断
2.2.11 task规则
2.2.12 buildSrc-构建代码
前面的在build.gradle中依赖到的自定义类ProjectVersion,这个类仅仅在构建过程中需要依赖到,如果都放在build.gradle中的话就会使得脚本杂乱,于是gradle提供了一个构建标准,在构建过程需要用到的类统一放在buildSrc目录下, java代码放在 buildSrc/src/main/java下,groovy代码放在buildSrc/src/main/groovy下。
2.2.13 构建过程生命周期钩子
三. 依赖管理
3.1 依赖配置
前面知道通过引入了java插件以后,可以通过compile, runtime,testCompile,testRuntime,archives,default等来进行声明源代码所需要的依赖。这些都是java插件引入的”配置“。
java插件声明的这些从依赖在构建中的生命周期进行划分, 这些配置下面对应的有一堆的dependency依赖
- compile: [dependency1, dependency2…]
- runtime: [dependency1, dependency2…]
- testCompile: [dependency1, dependency2…]
- testRuntime: [dependency1, dependency2…]
- archives: [dependency1, dependency2…]
除了从上面的这些维度进行划分依赖集合外,还可以通过自定义依赖配置,然后给这个依赖配置指派依赖。
简而言之:配置是gradle依赖的分组,java插件通过compile,runtime等做分组,还可以自己定义分组,然后指派依赖。
这一章为了更好地理解配置信息是如何存储,配合自己和访问的,看看gradle中的api接口是如何组织的。
3.1.1 配置api
配置是可以直接在项目的根级别添加和访问的,可以使用插件所提供的配置或者声明自己的配置。每个项目中都有一个configurationContainer类的容器来管理相应的配置。
3.1.2 自定义配置
1
2
3
4
5
6
configuration {
cargo { //声明一个自定义的依赖配置,然后下面就可以向这个依赖配置指派依赖
description = 'classpath for cargo ant tasks'
visible = false
}
}
3.2 依赖API
每个gradle项目都有一个依赖处理器实例, 每个依赖都是Dependency类型的一个实例。
1
2
3
dependencies {
configurationName dependencyNotation1, dependencyNotation2...
}
3.2.1 声明依赖
依赖属性:group, name, version, classifier
在gradle中依赖的类型分下面5种:
类型 | 描述 |
---|---|
外部模块依赖 | 依赖仓库中的外部类库,包括其提供的元数据 |
项目依赖 | 依赖其他gradle项目 |
文件依赖 | 依赖文件系统的中一系列文件 |
客户端模块依赖 | 依赖仓库中的外部类库, 具有声明元数据的能力 |
gradle运行时依赖 | 依赖gradle api或者gradle运行时的类库 |
1
2
3
4
5
//为自定义配置指定依赖,两种指派方式
dependencies {
cargo group: "org.codehaus.cargo", name: "cargo-ant", version: "1.3.1"
cargo "org.codehaus.cargo:cargo-ant:1.3.1"
}
额外知识,maven中的冲突依赖可以通过插件进行动态查看其中的传递依赖,但是gradle因为可以手动设置配置,不仅仅有java插件引入的冲突依赖,还可能是用户自己设置的配置里面的依赖冲突
3.2.2 传递性依赖
操作 | maven | gradle |
---|---|---|
剔除所有传递性依赖 | 不可以,强制传入 | 可以设置transitive=false |
剔除某个传递性依赖 | 可以 | 可以 |
如何剔除某个传递性依赖 | 单个项目通过插件图搜,多项目通过mvn dependency:tree搜索 | 只能通过gradle dependency进行搜索 |
1
2
3
4
5
6
dependencies {
cargo('org.codehaus.cargo:cargo-ant:1.3.1') {
transitive = false //完全剔除某个依赖的所有传递性依赖
exclude group: "xml-apis", module: 'xml-apis' //剔除某个传递性依赖
}
}
3.2.3 动态版本
当某个项目的构建需要使用最新的依赖包的时候,可以通过两种方式指定使用最新的包进行构建:
- version使用占位符 latest-integration, 例: org.codehaus.cargo:cargo-ant:latest-integration
- version使用占位符+号,例:org.codehaus.cargo:cargo-ant:1.+,表示使用最新的1.x版本
3.2.4 文件依赖
1
2
3
4
5
6
7
8
task copyDependencyToLocalDir(type: Copy) { //拷贝依赖到本地
from configurations.cargo.asFileTree
into "${System.properties['user.home']}/libs/cargo"
}
dependencies { //声明本地依赖
cargo fileTree(dir:"${System.properties['user.home']}/lib/cargo", include: '*.jar')
}
3.3 配置仓库
gradle中支持配置maven仓库, ivy仓库和扁平的本地系统文件仓库。
1
2
3
4
5
6
7
8
9
10
repositories {
mavenLocal()
mavenCentral()
maven {
name "自定义maven库"
url "http://repository-gradle.xxx/xxxxx/release/"
}
//本地仓库
flatDir(dir: "${System.properties['user.home']}/libs/cargo", name: 'local libs directory')
}
3.4 本地依赖缓存
3.5 版本冲突
gradle应对版本冲突的时候并不会主动通知, 默认处理冲突的逻辑是使用最新的版本。可以通过修改这个默认的逻辑,当遇到版本冲突的时候让构建失败。
1
2
3
configuration.cargo.resolutionStrategy {
failOnVersionConflict()
}
四. 多项目构建
4.1 setting.gradle
通过路径来声明子项目。如果子项目有层级关系,可以通过冒号:
进行分隔子项目的目录层级关系。
1
2
3
//setting.gradle
include 'module' //子项目与build.gradle同级
include 'module:second:third' //子项目在module/second/third目录下
4.2 settting api
构建之前会创建一个Setting类型的实例,是settings文件的映射表示
4.3 setting 执行
setting的执行是在初始化阶段。
4.3.1 获取setting文件
只要项目的根目录或者任何的子目录中存在构建脚本build.gradle,gradle便会从相应的位置进行构建。