Gradle 进阶
# 项目的生命周期
Gradle 项目的生命周期分为三大阶段:Initialization -> Configuration -> Execution. 每个阶段都有自己的职责,具体如下

Initialization 阶段主要目的是初始化构建,它又分为两个子过程
- 一个是执行 Init Script
- 一个是执行 Setting Script
init.gradle 文件会在每个项目 build 之前被调用,用于做一些初始化的操作,它主要有如下作用
- 配置内部的仓库信息(如公司的 maven 仓库信息)
- 配置一些全局属性
- 配置用户名及密码信息(如公司仓库的用户名和密码信息)
Setting Script 则更重要,它初始化了一次构建所参与的所有模块。
Configuration 阶段 这个阶段开始加载项目中所有模块的 Build Script。所谓 "加载" 就是执行 build.gradle 中的语句,根据脚本代码创建对应的 task ,最终根据所有 task 生成由 Task 组成的有向无环图 (Directed Acyclic Graphs),如下:

从而构成如下有向无环树

Execution 阶段 这个阶段会根据上个阶段构建好的有向无环图,按着顺序执行 Task【Action 动作】。
# Settings 文件
首先对 settings 文件的几点说明:
- 作用:主要是在项目初始化阶段确定一下引入哪些工程需要加入到项目构建中,为构建项目工程树做准备。
- 工程树:gradle 中有工程树的概念,类似于 maven 中的 project 与 module。
- 内容:里面主要定义了当前 gradle 项目及子 project 的项目名称 。
- 位置:必须放在根工程目录下。
- 名字:为 settings.gradle 文件,不能发生变化。
- 对应实例:与 org.gradle.api.initialization.Settings 实例是一一对应的关系。每个项目只有一个 settings 文件。
- 关注:作为开发者我们只需要关注该文件中的 include 方法即可。使用相对路径【 : 】引入子工程。
- 一个子工程只有在 setting 文件中配置了才会被 gradle 识别,这样在构建的时候才会被包含进去 。
//根工程项目名
rootProject.name = 'root'
//包含的子工程名称
include 'subject01'
include 'subject02'
include 'subject03'
//包含的子工程下的子工程名称
include 'subject01:subproject011'
include 'subject01:subproject012'
2
3
4
5
6
7
8
9
项目名称中 ":" 代表项目的分隔符, 类似路径中的 "/"。如果以 ":" 开头则表示相对于 root project 。然后 Gradle 会为每个带有 build.gradle 脚本文件的工程构建一个与之对应的 Project 对象 。
# Task
项目实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译 Java 源代码,拷贝文件,打包 Jar 文件,甚至可以是执行一个系统命令。另外,一个 Task 可以读取和设置 Project 的 Property 以完成特定的操作。
# 任务入门
Build Script Basics (gradle.org) (opens new window)
task A {
println "root taskA"
doFirst() {
println "root taskA doFirst"
}
doLast() {
println "root taskA doLast"
}
}
2
3
4
5
6
7
8
9
在文件所在的目录执行命令: gradle A。
提示 1 :task 的配置段是在配置阶段完成
提示 2 :task 的 doFirst、doLast 方法是执行阶段完成,并且 doFirst 在 doLast 执行之前执行。
提示 3: 区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行
# 任务的行为
def map = new HashMap<String, Object>();
//action属性可以设置为闭包, 设置task自身的行为
map.put("action", { println "taskD.." })
task(map, "a") {
description 'taskA description....'
group "dfd"
//在task内部定义doFirst、 doLast行为
doFirst {
def name = 'doFirst..'
println name
}
doLast {
def name = 'doLast..'
println name
}
}
//在task外部定义doFirst、 doLast行为
a.doFirst {
println it.description
}
a.doLast {
println it.group
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
底层原理分析:无论是定义任务自身的 action,还是添加的 doLast、doFirst 方法,其实底层都被放入到一个 Action 的 List 中了,最初这个 action List 是空的,当我们设置了 action【任务自身的行为】, 它先将 action 添加到列表中,此时列表中只有一个 action, 后续执行 doFirst 的时候 doFirst 在 action 前面添加,执行 doLast 的时候 doLast 在 action 后面添加。doFirst 永远添加在 actions List 的第一位,保证添加的 Action 在现有的 action List 元素的最前面;doLast 永远都是在 action List 末尾添加,保证其添加的 Action 在现有的 action List 元素的最后面。一个往前面添加,一个往后面添加,最后这个 action List 就按顺序形成了 doFirst、doSelf、doLast 三部分的 Actions,就达到 doFirst、doSelf、doLast 三部分的 Actions 顺序执行的目的。
# 任务的依赖方式
Task 之间的依赖关系可以在以下几部分设置:
- 参数依赖
- 内部依赖
- 外部依赖
方式一:参数方式依赖
task A {
doLast {
println "TaskA.."
}
}
task 'B' {
doLast {
println "TaskB.."
}
}
//参数方式依赖: dependsOn后面用冒号
task 'C'(dependsOn: ['A', 'B']) {
doLast {
println "TaskC.."
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
方式二:内部依赖
//参数方式依赖
task 'C' {
//内部依赖:dependsOn后面用 = 号
dependsOn = [A, B]
doLast {
println "TaskC.."
}
}
2
3
4
5
6
7
8
方式三:外部依赖
//外部依赖:可变参数,引号可加可不加
C.dependsOn(B,'A')
2
当然:task 也支持跨项目依赖
在 subproject01 工程的 build.gradle 文件中定义
task A {
doLast {
println "TaskA.."
}
}
2
3
4
5
在 subproject02 工程的 build.gradle 文件中定义
task B {
dependsOn(":subproject01:A") //依赖根工程下的subject01中的任务A : 跨项目依赖。
doLast {
println "TaskB.."
}
}
2
3
4
5
6
测试:gradle B , 控制台显示如下
拓展 1:当一个 Task 依赖多个 Task 的时候,被依赖的 Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响。
拓展 2:重复依赖的任务只会执行一次,比如:
A -> B、C
B -> C
任务 A 依赖任务 B 和任务 C、任务 B 依赖 C 任务。执行任务 A 的时候,显然任务 C 被重复依赖了,C 只会执行一次。
# 任务执行
任务执行语法:gradle [taskName...] [--option-name...]
分类 | 解释 |
---|---|
常见的任务(*) | gradle build: 构建项目:编译、测试、打包等操作 gradle run : 运行一个服务,需要 application 插件支持,并且指定了主启动类才能运行 gradle clean: 请求当前项目的 build 目录 gradle init : 初始化 gradle 项目使用 gradle wrapper: 生成 wrapper 文件夹的。 gradle wrapper 升级 wrapper 版本号:gradle wrapper --gradle-version=4.4 gradle wrapper --gradle-version 5.2.1 --distribution-type all : 关联源码用 |
项目报告相关任务 | gradle projects : 列出所选项目及子项目列表,以层次结构的形式显示 gradle tasks: 列出所选项目【当前 project, 不包含父、子】的已分配给任务组的那些任务。 gradle tasks --all : 列出所选项目的所有任务。 gradle tasks --group="build setup": 列出所选项目中指定分组中的任务。 gradle help --task someTask : 显示某个任务的详细信息 gradle dependencies : 查看整个项目的依赖信息,以依赖树的方式显示 gradle properties 列出所选项目的属性列表 |
调试相关选项 | -h,--help: 查看帮助信息 -v, --version: 打印 Gradle、 Groovy、 Ant、 JVM 和操作系统版本信息。 -S, --full-stacktrace: 打印出所有异常的完整 (非常详细) 堆栈跟踪信息。 -s,--stacktrace: 打印出用户异常的堆栈跟踪 (例如编译错误)。 -Dorg.gradle.daemon.debug=true: 调试 Gradle 守护进程。 -Dorg.gradle.debug=true: 调试 Gradle 客户端 (非 daemon) 进程。 -Dorg.gradle.debug.port=(port number): 指定启用调试时要侦听的端口号。默认值为 5005。 |
性能选项:【备注:在 gradle.properties 中指定这些选项中的许多 选项,因此不需要命 令行标志】 | --build-cache, --no-build-cache: 尝试重用先前版本的输出。默认关闭 (off)。 --max-workers: 设置 Gradle 可以使用的 woker 数。默认值是处理器数。 -parallel, --no-parallel: 并行执行项目。有关此选项的限制,请参阅并行项目执行。 默认设置为关闭 (off) |
守护进程选项 | --daemon, --no-daemon: 使用 Gradle 守护进程运行构建。默认是 on --foreground: 在前台进程中启动 Gradle 守护进程。 -Dorg.gradle.daemon.idletimeout=(number of milliseconds): Gradle Daemon 将在这个空闲时间的毫秒数之后停止自己。默认值为 10800000 (3 小时)。 |
日志选项 | -Dorg.gradle.logging.level=(quiet,warn,lifecycle,info,debug): 通过 Gradle 属性设置日志记录级别。 -q, --quiet: 只能记录错误信息 -w, --warn: 设置日志级别为 warn -i, --info: 将日志级别设置为 info -d, --debug: 登录调试模式 (包括正常的堆栈跟踪) |
其它 (*) | -x:-x 等价于: --exclude-task : 常见 gradle -x test clean build --rerun-tasks: 强制执行任务,忽略 up-to-date , 常见 gradle build --rerun-tasks --continue: 忽略前面失败的任务,继续执行,而不是在遇到第一个失败时立即停止执行。每 个遇到的故障都将在构建结束时报告,常见:gradle build --continue。 gradle init --type pom : 将 maven 项目转换为 gradle 项目 (根目录执行) gradle [taskName] : 执行自定义任务 |
Command-Line Interface (gradle.org) (opens new window)
拓展 1:gradle 任务名是缩写:任务名支持驼峰式命名风格的任务名缩写,如:connectTask 简写为:cT, 执行任务 gradle cT。
拓展 2: 前面提到的 Gradle 指令本质:一个个的 task [任务], Gradle 中所有操作都是基于任务完成的。
拓展 3:gradle 默认各指令之间相互的依赖关系:
# 任务定义方式
任务定义方式,总体分为两大类:一种是通过 Project 中的 task () 方法,另一种是通过 tasks 对象的 create 或者 register 方法。
task('A', {//任务名称,闭包都作为参数
println "taskA..."
})
task('B') {//闭包作为最后一个参数可以直接从括号中拿出来
println "taskB..."
}
task C {//groovy语法支持省略方法括号:上面三种本质是一种
println "taskC..."
}
def map = new HashMap<String, Object>();
map.put("action", { println "taskD.." }) //action属性可以设置为闭包
task(map, "D");
tasks.create('E') {//使用tasks的create方法
println "taskE.."
}
tasks.register('f') { //注:register执行的是延迟创建。也即只有当task被需要使用的时候才会被创建。
println "taskF...."
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
当然:我们也可以在定义任务的同时指定任务的属性,具体属性有:
配置项 | 描述 | 默认值 |
---|---|---|
type | 基于一个存在的 Task 来创建,和我们类继承差不多 | DefaultTask |
overwrite | 是否替换存在的 Task,这个和 type 配合起来用 | false |
dependsOn | 用于配置任务的依赖 | [] |
action | 添加到任务中的一个 Action 或者闭包 | null |
description | 用于配置任务的描述 | null |
group | 用于配置任务的分组 | null |
在定义任务时也可以给任务分配属性:定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性:
//1.F是任务名,前面通过具名参数给map的属性赋值,以参数方式指定任务的属性信息
task(group: "dfd", description: "this is task B", "F")
//2.H是任务名,定义任务的同时,在内部直接指定属性信息
task("H") {
group("dfd")
description("this is the task H")
}
//3.Y是任务名,给已有的任务 在外部直接指定属性信息
task "y" {}
y.group = "dfd"
clean.group("dfd") //案例:给已有的clean任务重新指定组信息
2
3
4
5
6
7
8
9
10
11
可以在 idea 中看到: 上面自定义的那几个任务和 gradle 自带的 clean 任务已经跑到:dfd 组了。
# 任务类型
前面我们定义的 task 都是 DefaultTask 类型的,如果要完成某些具体的操作完全需要我们自己去编写 gradle 脚本,势必有些麻烦,那有没有一些现成的任务类型可以使用呢?有的,Gradle 官网给出了一些现成的任务类型帮助我们快速完成想要的任务,我们只需要在创建任务的时候,指定当前任务的类型即可,然后即可使用这种类型中的属性和 API 方法了。
常见任务类型 | 该类型任务的作用 |
---|---|
Delete | 删除文件或目录 |
Copy | 将文件复制到目标目录中。此任务还可以在复制时重命名和筛选文件 |
CreateStartScripts | 创建启动脚本 |
Exec | 执行命令行进程 |
GenerateMavenPom | 生成 Maven 模块描述符 (POM) 文件 |
GradleBuild | 执行 Gradle 构建 |
Jar | 组装 JAR 归档文件 |
JavaCompile | 编译 Java 源文件 |
Javadoc | 为 Java 类生成 HTML API 文档 |
PublishToMavenRepository | 将 MavenPublication 发布到 mavenartifactrepostal |
Tar | 组装 TAR 存档文件 |
Test | 执行 JUnit (3.8.x、4.x 或 5.x) 或 TestNG 测试 |
Upload | 将 Configuration 的构件上传到一组存储库 |
War | 组装 WAR 档案 |
Zip | 组装 ZIP 归档文件。默认是压缩 ZIP 的内容 |
Gradle DSL Version 7.5.1 (opens new window)
具体使用例如:
tasks.register('myClean', Delete) {
delete buildDir
}
2
3
在命令行执行 gradle myClean
发现就可以将当前 project 的 build 目录删除。
当然除了 gradle 自带的 task 类型,我们也可以自定义 task 类型:
def myTask = task MyDefinitionTask(type: CustomTask)
myTask.doFirst() {
println "task 执行之前 执行的 doFirst方法"
}
myTask.doLast() {
println "task 执行之后 执行的 doLast方法"
}
class CustomTask extends DefaultTask {
//@TaskAction表示Task本身要执行的方法
@TaskAction
def doSelf() {
println "Task 自身 在执行的in doSelf"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试: gradle MyDefinitionTask
# 任务的执行顺序
在 Gradle 中,有三种方式可以指定 Task 执行顺序:
dependsOn 强依赖方式
通过 Task 输入输出
通过 API 指定执行顺序
详细请参考官网:Task - Gradle DSL Version 7.5.1 (opens new window)
# 动态分配任务
gradle 的强大功能不仅仅用于定义任务的功能。例如,可以使用它在循环中注册同一类型的多个任务
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "I'm task number $counter"
}
}
}
// 构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。
tasks.named('task0') { dependsOn('task2', 'task3') }
2
3
4
5
6
7
8
9
使用 gradle -q task0
执行
# 任务的关闭与开启
每个任务都有一个 enabled 默认为的标志 true 。将其设置为 false阻止执行任何任务动作。禁用的任务将标记为 跳过 。
task disableMe {
doLast {
println 'This task is Executing...'
}
enabled(true)//直接设置任务开启, 默认值为true
}
//disableMe.enabled = false //设置关闭任务
2
3
4
5
6
7
# 任务的超时
每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果 --continue 使用,其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时。
task a() {
doLast {
Thread.sleep(1000)
println "当前任务a执行了"
}
timeout = Duration.ofMillis(500)
}
task b() {
doLast {
println "当前任务b执行了"
}
}
2
3
4
5
6
7
8
9
10
11
12
在控制台使用: gradle a b 测试会发现执行 a 的时候,由于 a 执行超时,抛出异常,所以没有继续往下执行【b 也没执行】。然后在控制台使用: gradle a b –continue, 测试会发现 a 虽然执行失败,但是 b 还是执行了 。
# 任务的查找
task dongdong {
doLast {
println "东东很大"
}
}
//根据任务名查找
tasks.findByName("dongdong").doFirst({ println "东东好大啊" })
tasks.getByName("dongdong").doFirst({ println "东东巨大啊" })
//根据任务路径查找【相对路径】
tasks.findByPath(":dongdong").doFirst({ println "东东好最啊" })
tasks.getByPath(":dongdong").doFirst({ println "东东好小啊" })
2
3
4
5
6
7
8
9
10
11
# 任务的规则
当我们执行、依赖一个不存在的任务时,Gradle 会执行失败,报错误信息。那我们能否对其进行改进,当执行一个不存在的任务时,不是报错而是打印提示信息呢?
task hello {
doLast {
println 'hello 你好'
}
}
tasks.addRule("对该规则的一个描述,便于调试、查看等") {
String taskName ->
task(taskName) {
doLast {
println "该${taskName}任务不存在,请查证后再执行"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
测试:使用 gradle abc hello 进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况 。
# 任务的 onlyIf 断言
断言就是一个条件表达式。Task 有一个 onlyIf 方法。它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。具体案例如下所示:
task hello {
doLast {
println 'hello 东东'
}
}
//如果hello任务没有dd这个属性,就会执行
hello.onlyIf { !project.hasProperty('dd') }
2
3
4
5
6
7
测试:通过 - P 为 Project 添加 dd 属性。 gradle hello -Pdd
# 默认任务
Gradle 允许您定义一个或多个在没有指定其他任务时执行的默认任务。
defaultTasks 'myClean', 'myRun'
tasks.register('myClean') {
doLast {
println 'Default Cleaning!'
}
}
tasks.register('myRun') {
doLast {
println 'Default Running!'
}
}
tasks.register('other') {
doLast {
println "I'm not a default task!"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Gradle 中的文件操作
几种常见的文件操作方式:
- 本地文件
- 文件集合
- 文件树
- 文件拷贝
- 归档文件
# 本地文件
使用 Project.file(java.lang.Object)方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前 project [根 project 或者子 project] 的目录。其实使用 Project.file (java.lang.Object) 方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样。示例代码如下:
//使用相对路径
File configFile = file('src/conf.xml');
configFile.createNewFile();
// 使用绝对路径
configFile = file('D:\\conf.xml');
println(configFile.createNewFile());
// 使用一个文件对象
configFile = new File('src/config.xml');
println(configFile.exists());
2
3
4
5
6
7
8
9
# 文件集合
文 件 集 合 就 是 一 组 文 件 的 列 表,在 Gradle 中,文 件 集 合 用 FileCollection 接 口 表 示 。 我 们 可 以 使 用
Project.files (java.lang.Object []) 方法来获得一个文件集合对象,如下代码创建一个 FileCollection 实例:
def collection = files('src/test1.txt', new File('src/test2.txt'), ['src/test3.txt', 'src/test4.txt'])
collection.forEach() { File it ->
it.createNewFile() //创建该文件
// println it.name //输出文件名
}
Set set1 = collection.files // 把文件集合转换为java中的Set类型
Set set2 = collection as Set
List list = collection as List// 把文件集合转换为java中的List类型
//for (item in list) {
// println item.name
//}
def union = list + files('src/test5.txt') // 添加或者删除一个集合
def minus = union - files('src/test3.txt')
minus.forEach() { File it ->
println it.name
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对于文件集合我们可以遍历它;也可以把它转换成 java 类型;同时还能使用 + 来添加一个集合,或使用 - 来删除集合。
# 文件树
文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。我们可以使用 Project.fileTree(java.util.Map)方法来创建文件树对象,还可以使用过虑条件来包含或排除相关文件。示例代码如下
// 第一种方式:使用路径创建文件树对象,同时指定包含的文件
def tree = fileTree('src/main').include('**/*.java')
//第二种方式:通过闭包创建文件树:
//tree = fileTree('src/main') {
// include '**/*.java'
//}
//第三种方式:通过路径和闭包创建文件树:具名参数给map传值
//tree = fileTree(dir: 'src/main', include: '**/*.java')
tree = fileTree(dir: 'src/main', includes: ['**/*.java', '**/*.xml', '**/*.txt'], exclude: '**/*test*/**')
tree.each { File file -> // 遍历文件树的所有文件
println file
println file.name
}
2
3
4
5
6
7
8
9
10
11
12
13
# 文件拷贝
我们可以使用 Copy 任务来拷贝文件,通过它可以过虑指定拷贝内容,还能对文件进行重命名操作等。Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用 CopySpec.from (java.lang.Object []) 方法指定原文件;使用 CopySpec.into (java.lang.Object) 方法指定目标目录。示例代码如下
//将resources下的文件拷贝到build的config文件夹下
task copyTask(type: Copy) {
from 'src/main/resources'
into 'build/config'
}
2
3
4
5
from () 方法接受的参数和文件集合时 files () 一样。
- 当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝)。
- 当参数为一个文件时,该文件会被拷贝到指定目录。
- 如果参数指定的文件不存在,就会被忽略;当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。
into () 方法接受的参数与本地文件时 file () 一样。 示例代码如下:
task copyTask2(type: Copy) {
// 拷贝src/main/webapp目录下所有的文件
from 'src/main/webapp'
// 拷贝单独的一个文件
// from 'src/main/java/com/example/boot/CcProperties.java'
// 从Zip压缩文件中拷贝内容
from zipTree('src/main/assets.zip')
// 拷贝到的目标目录
into 'build/explodedWar'
}
2
3
4
5
6
7
8
9
10
在拷贝文件的时候还可以添加过虑条件来指定包含或排除的文件,示例如下:
task copyTaskWithPatterns(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
exclude { details -> details.file.name.endsWith('.html') }
}
2
3
4
5
6
7
在拷贝文件的时候还可以对文件进行重命名操作,示例如下:
task rename(type: Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
// 使用一个闭包方式重命名文件
rename { String fileName ->
fileName.replace('-staging-', '')
}
}
2
3
4
5
6
7
8
在上面的例子中我们都是使用 Copy 任务来完成拷贝功能的,那么有没有另外一种方式呢?答案是肯定的,那就是 Project.copy (org.gradle.api.Action) 方法。下面示例展示了 copy () 方法的使用方式:
task copyMethod {
doLast {
copy {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
}
}
}
2
3
4
5
6
7
8
9
10
或者使用 project 对象的 copy 方法:
copy {
//相对路径或者绝对路径
from file('src/main/resources/ddd.txt') //file也可以换成new File()
into this.buildDir.absolutePath
}
2
3
4
5
执行 gradle build
指令即可。去 build 目录的本地磁盘查看,就能看到。
# 归档文件
通常一个项目会有很多的 Jar 包,我们希望把项目打包成一个 WAR,ZIP 或 TAR 包进行发布,这时我们就可以使用 Zip,Tar,Jar,War 和 Ear 任务来实现,不过它们的用法都一样,所以在这里我只介绍 Zip 任务的示例。首先,创建一个 Zip 压缩文件,并指定压缩文件名称,如下代码所示:
apply plugin: 'java'
version = 1.0
task myZip(type: Zip) {
from 'src/main'
into 'build' //保存到build目录中
baseName = 'myZIP'
}
println myZip.archiveName
2
3
4
5
6
7
8
执行命令 gradle -q myZip
,会在 build 文件夹 distributions 中生成 myZIP-1.0.zip
最后,我们可以使用 Project.zipTree (java.lang.Object) 和 Project.tarTree (java.lang.Object) 方法来创建访问 Zip 压缩包的文件树对象,示例代码如下:
// 使用zipTree
FileTree zip = zipTree('build/distributions/myZIP-1.0.zip')
// 使用tarTree
FileTree tar = tarTree('someFile.tar')
2
3
4
在这里,我们介绍了 Gradle 对本地文件、文件集合、文件树、文件拷贝和归档文件的操作方式。更详细的请参考官方文档:Working With Files (gradle.org) (opens new window)。
# Dependencies
# 依赖的方式
Gradle 中的依赖分别为直接依赖,项目依赖,本地 jar 依赖 。
dependencies {
//①.依赖当前项目下的某个模块[子工程]
implementation project(':subject01')
//②.直接依赖本地的某个jar文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//②.配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
//③.直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2'
}
2
3
4
5
6
7
8
9
10
直接依赖 :在项目中直接导入的依赖,就是直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2'
上面是简写法,完整版是:
implementation group: 'org.apache.logging.log4j', name: 'log4j', version: '2.17.2'
group/name/version 共同定位一个远程仓库,version 最好写一个固定的版本号,以防构建出问题,implementation 类似 maven 中的依赖的 scope,对比 maven 中的依赖:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
8
项目依赖:从项目的某个模块依赖另一个模块
implementation project(':subject01')
这种依赖方式是直接依赖本工程中的 libary module,这个 libary module 需要在 setting.gradle 中配置。
本地 jar 依赖:本地 jar 文件依赖,一般包含以下两种方式
//直接依赖某文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
2
3
4
# 依赖的下载
当执行 build 命令时,gradle 就会去配置的依赖仓库中下载对应的 Jar,并应用到项目中。
# 依赖的类型
类似于 Maven 的 scope 标签,gradle 也提供了依赖的类型,具体如下所示:
命令 | 说明 |
---|---|
compileOnly | 由 java 插件提供,曾短暂的叫 provided,后续版本已经改成了 compileOnly,适用于编译期需要而不需要打包的情况 |
runtimeOnly | 由 java 插件提供,只在运行期有效,编译时不需要,比如 mysql 驱动包。取代老版本中被移除的 runtime |
implementation | 由 java 插件提供,针对源码 [src/main 目录],在编译、运行时都有效,取代老版本中被移除的 compile |
testCompileOnly | 由 java 插件提供,用于编译测试的依赖项,运行时不需要 |
testRuntimeOnly | 由 java 插件提供,只在测试运行时需要,而不是在测试编译时需要,取代老版本中被移除的 testRuntime |
testImplementation | 由 java 插件提供,针对测试代码 [src/test 目录] 取代老版本中被移除的 testCompile |
providedCompile | war 插件提供支持,编译、测试阶段代码需要依赖此类 jar 包,而运行阶段容器已经提供了相应的支持,所 以无需将这些文件打入到 war 包中了;例如 servlet-api.jar、jsp-api.jar |
compile | 编译范围依赖在所有的 classpath 中可用,同时它们也会被打包。在 gradle 7.0 已经移除 |
runtime | runtime 依赖在运行和测试系统的时候需要,在编译的时候不需要,比如 mysql 驱动包。在 gradle 7.0 已经移除 |
api | java-library 插件提供支持,这些依赖项可以传递性地导出给使用者,用于编译时和运行时。取代老版本中被 移除的 compile |
compileOnlyApi | java-library 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。 |
官方文档参考:
The Java Library Plugin (gradle.org) (opens new window):各个依赖范围的关系和说明
Upgrading your build from Gradle 6.x to the latest (opens new window):依赖范围升级和移除
The Java Library Plugin (gradle.org) (opens new window):API 和 implemention 区别
The Java Plugin (gradle.org) (opens new window):执行 java 命令时都使用了哪些依赖范围的依赖
java 插件提供的功能,java-library 插件都提供。
# api 与 implementation 区别
api | implementation | |
---|---|---|
编译时 | 能进行依赖传递,底层变,全部都需要改变。编译速度慢 | 不能进行依赖传递,底层变,不用全部都要变。编译速度快 |
运行时 | 运行时会进行加载,所有模块的 Class 都要被加载 | 运行时会进行加载,所有模块的 Class 都要被加载 |
应用场景 | 适用于多模块依赖,避免重复依赖模块 | 多数情况下使用 implementation |
如下图:
编译时:如果 libC 的内容发生变化,由于使用的是 api 依赖,依赖会传递,所以 libC、libA、projectX 都要发生变化,都需要重新编译,速度慢,运行时:libC、libA、projectX 中的 class 都要被加载。
编译时:如果 libD 的内容发生变化,由于使用的是 implemetation 依赖,依赖不会传递,只有 libD、libB 要变化并重新编译,速度快。
运行时:libC、libA、projectX 中的 class 都要被加载。
api 和 implementation 案例分析
api 的适用场景是多 module 依赖,moduleA 工程依赖了 module B,同时 module B 又需要依赖了 module C,modelA 工程也需要去依赖 module C,这个时候避免重复依赖 module,可以使用 module B api 依赖的方式去依赖 module C,modelA 工程只需要依赖 moduleB 即可。
有 ABCD 四个模块:
- A implementation B,B implementation C,则 A 不能使用 C 。
- A implementation B,B api C,则 A 能使用 C 。
- A implementation B,B implementation C,C api D, 则 B 可以使用 D,A 不可以使用 D。
- A implementation B,B api C,C api D,A 可以使用 D。
- 不管 ABCD 在何处被添加到类路径都一样,在运行时这些模块中的 class 都是要被加载的。
总之,除非涉及到多模块依赖,为了避免重复依赖,使用 api,其它情况优先选择 implementation,拥有大量的 api 依赖项会显著增加构建时间。
# 依赖冲突及解决方案
依赖冲突是指 在编译过程中, 如果存在某个依赖的多个版本, 构建系统应该选择哪个进行构建的问题
,如下所示:
A、B、C 都是本地子项目 module,log4j 是远程依赖。
编译时:B 用 1.4.2 版本的 log4j,C 用 2.2.4 版本的 log4j,B 和 C 之间没有冲突
打包时:只能有一个版本的代码最终打包进最终的 A 对应的 jar|war 包,对于 Gradle 来说这里就有冲突了
案例演示:我们在 build.gradle 引入依赖库
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
}
2
3
4
5
修改 build.gradle
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
implementation 'org.slf4j:slf4j-api:1.4.0'
}
2
3
4
5
6
如上所示:默认下,Gradle 会使用最新版本的 jar 包【考虑到新版本的 jar 包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。当然除此之外,Gradle 也为我们提供了一系列的解决依赖冲突的方法: exclude 移除一个依赖,不允许依赖传递,强制使用某个版本。
Exclude 排除某个依赖
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation('org.hibernate:hibernate-core:3.6.3.Final'){
//排除某一个库(slf4j)依赖:如下三种写法都行
exclude group: 'org.slf4j'
exclude module: 'slf4j-api'
exclude group: 'org.slf4j',module: 'slf4j-api'
}
//排除之后,使用手动的引入即可。
implementation 'org.slf4j:slf4j-api:1.4.0'
}
2
3
4
5
6
7
8
9
10
11
12
不允许依赖传递
在添加依赖项时,如果设置 transitive 为 false,表示关闭依赖传递。即内部的所有依赖将不会添加到编译和运行时的类路径。
强制使用某个版本
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation('org.hibernate:hibernate-core:3.6.3.Final')
//强制使用某个版本!!【官方建议使用这种方式】
implementation('org.slf4j:slf4j-api:1.4.0!!')
//这种效果和上面那种一样,强制指定某个版本
implementation('org.slf4j:slf4j-api:1.4.0') {
version {
strictly("1.4.0")
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
拓展:我们可以先查看当前项目中到底有哪些依赖冲突
//下面我们配置,当 Gradle 构建遇到依赖冲突时,就立即构建失败
configurations.all() {
Configuration configuration ->
//当遇到版本冲突时直接构建失败
configuration.resolutionStrategy.failOnVersionConflict()
}
2
3
4
5
6
# Gradle 插件
# 使用插件的原因
简单的说,通过应用插件我们可以
- 促进代码重用、 减少功能类似代码编写、 提升工作效率。
- 促进项目更高程度的模块化、 自动化、 便捷化。
- 可插拔式的的扩展项目的功能。
# 插件的作用
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
- 可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等。
- 可以添加依赖配置到项目中。
- 可以向项目中拓展新的扩展属性、方法等。
- 可以对项目进行一些约定,如应用 Java 插件后,约定 src/main/java 目录是我们的源代码存在位置,编译时编译这个目录下的 Java 源代码文件。
# 插件的分类和使用
第一种:脚本插件
脚本插件的本质就是一个脚本文件,使用脚本插件时通过 apply from: 将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如:
//version.gradle文件
ext {
username = "东东"
cfgs = [compileSdkVersion: JavaVersion.VERSION_1_8]
spring = [version: '5.0.0']
}
2
3
4
5
6
下面将将在构建文件中使用这个脚本文件,具体如下:
//build.gradle文件
//map作为参数
apply from: 'version.gradle'
task taskVersion {
doLast {
println "姓名为:${username},JDK版本是${cfgs.compileSdkVersion},版本号是${spring.version}"
}
}
2
3
4
5
6
7
8
意义:脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
第二种:对象插件之内部插件 [核心插件]
二进制插件 [对象插件] 就是实现了 org.gradle.api.Plugin 接口的插件,每个 Java Gradle 插件都有一个 plugin id。
可通过如下方式使用一个 Java 插件:
apply plugin : 'java' //map具名参数方式
或者:
//也可以使用闭包作为project.apply方法的一个参数
apply{
plugin 'java'
}
2
3
4
通过上述代码就将 Java 插件应用到我们的项目中了,对于 Gradle 自带的核心插件都有唯一的 plugin id,其中 java 是 Java 插件的 plugin id, 这个 plugin id 必须是唯一的,可使用应用包名来保证 plugin id 的唯一性。这里的 java 对应的具体类型是 org.gradle.api.plugins.JavaPlugin
,所以可以使用如下方式使用 Java 插件。
//使用方式1: Map具名参数,全类名
apply plugin:org.gradle.api.plugins.JavaPlugin
//org.gradle.api.plugins默认导入: 使用方式2
apply plugin:JavaPlugin
apply plugin: 'java' //核心插件,无需事先引入, 使用方式3:插件的id
2
3
4
5
Gradle 中提供的二进制插件【核心插件】,可参考:Gradle Plugin Reference (opens new window)。
第三种:对象插件之第三方插件
如果是使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径
//使用传统的应用方式,必须要放在文件的开始
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
jcenter()
} // 此处先引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
//再应用插件
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,不必写版本号
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但是如果是第三方插件已经被托管在 https://plugins.gradle.org/ 网站上,就可以不用在 buildscript 里配置 classpath 依赖了,直接使用新出的 plugins DSL 的方式引用。
plugins {
id 'org.springframework.boot' version '2.4.1'
}
2
3
注意
- 如果使用老式插件方式 buildscript {} 要放在 build.gradle 文件的最前面,而新式 plugins {} 没有该限制。
- 托管在网站 gradle 插件官网的第三方插件有两种使用方式,一是传统的 buildscript 方式,一种是 plugins DSL 方式 。
第四种:对象插件之用户自定义插件
interface GreetingPluginExtension {
Property<String> getMessage()
Property<String> getGreeter()
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello') {
doLast {
println "${extension.message.get()} from ${extension.greeter.get()}"
}
}
}
}
apply plugin: GreetingPlugin
// Configure the extension using a DSL block
greeting {
message = 'Hi'
greeter = 'Gradle'
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
参考地址:Developing Custom Gradle Plugins (opens new window)
我们直接执行 hello 任务 ./gradle hello
即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本
Project,而其他的 Project 不能使用。
# buildSrc 项目
buildSrc 是 Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。
首先先建立一个名为 buildSrc 的 java Module, 将 buildSrc 从 included modules 移除,重新构建,然后只保留 build.gradle 和 src/main 目录,其他全部删掉,注意名字一定是 buildSrc,不然会找不到插件。
然后修改 Gradle 中的内容
apply plugin: 'groovy' //必须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter()
mavenCentral() //必须
}
//把项目入口设置为src/main/groovy
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 创建入口目录,在 src/main 下创建代码入口目录
- 然后实现插件代码 Text.groovy,注意文件后缀为 groovy,文件要引入 package com.gradle
package com.gradle
import org.gradle.api.Plugin
import org.gradle.api.Project
class Text implements Plugin<Project> {
@Override
void apply(Project project) {
project.task("dfd") {
doLast {
println("自定义dfd插件")
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
接下来在 main 目录下创建 resources 目录,在 resources 目录下创建 META-INF 目录,在 META-INF 目录下创建 gradle-plugins 目录,在 gradle-plugins 目录下创建 properties 文件。
properties 文件可以自己命名,但是要以.properties 结尾,比如 com.dfd.plugin.properties, 其 com.dfd.plugin 就是定义的包名路径。
最后需要在 properties 文件中指明我们实现插件的全类名 implementation-class=com.gradle.Text
到目前为止我们的插件项目已经写完了,在 module 引入我们写的插件 apply plugin:'com.dfd.plugin',然后执行插件的。
这种形式的写法,在我们整个工程的 module 都可以使用,但也只是限制在本工程,其他工程不能使用。
改进
第二种写插件的方式他只能在本工程中使用,而其他的项目工程不能使用,有时候我们需要一个插件在多个工程中使用,这时候我们就需要把插件上传 maven 中。
- 第一步:首先将上述 buildSrc 目录复制一份,修改文件夹名,然后在 settings.gradle 文件中使用 include 引入
- 第二步:修改 build.gradle 文件,发布到 maven 仓库中
apply plugin: 'groovy' //必须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter()
mavenCentral() //必须
}
sourceSets { //把项目入口设置为src/main/groovy
main {
groovy {
srcDir 'src/main/groovy'
}
}
}
publishing {
publications {
myLibrary(MavenPublication) {
groupId = 'com.dfd.plugin' //指定GAV坐标信息
artifactId = 'library'
version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories {
maven { url "$rootDir/lib/release" }
//发布项目到私服中
// maven {
// name = 'myRepo' //name属性可选,表示仓库名称,url必填
// //发布地址:可以是本地仓库或者maven私服
// //url = layout.buildDirectory.dir("repo")
// //url='http://my.org/repo'
// // change URLs to point to your repos, e.g. http://my.org/repo
// //认证信息:用户名和密码
// credentials {
// username = 'joe'
// password = 'secret'
// }
// }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- 第三步:执行 publish 指令,发布到根 project 或者 maven 私服仓库。
- 第四步:使用插件,在项目级 build.gradle 文件中将插件添加到 classpath:
buildscript {
repositories {
maven { url "$rootDir/lib/release" }
}
dependencies {
classpath "com.atguigu.plugin:library:1.1"
}
}
apply plugin: 'java'
//是在 buildSrc 中定义的插件 ID
apply plugin: 'com.dfd.plugin'
2
3
4
5
6
7
8
9
10
11
- 第五步:执行 gradle build 指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了。
# 插件的关注点
- 插件的引用
apply plugin: '插件名'
主要的功能 [任务]
当我们在工程中引入插件后,插件会自动的为我们的工程添加一些额外的任务来完成相应的功能。以 Java 插件为例,当我们加入 java 插件之后,就加入了如下功能:
具体大家可通过 gradle tasks 查看加入某个插件前后的区别。
说明:Gradle 中的任务依赖关系是很重要的,它们之间的依赖关系就形成了构建的基本流程。
- 工程目录结构
一些插件对工程目结构有约定,所以我们一般遵循它的约定结构来创建工程,这也是 Gradle 的 约定优于配置
原则。例如 java 插件规定的项目源集目录结构如下所示:
如果要使用某个插件就应该按照它约定的目录结构设置,这样能大大提高我们的效率,当然各目录结构也可以自己定义。
- 依赖管理
比如前面我们提到的依赖的类型【依赖管理】部分,不同的插件提供了不同的依赖管理。
- 常用的属性
例如:Java 插件会为工程添加一些常用的属性,我们可以直接在编译脚本中直接使用。
属性名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
reportsDirName | String | reports | 生成报告的目录名称 |
reportsDir | File(只读) | buildDir/reportsDirName | 生成报告的目录 |
testResultsDirName | String | test-results | 生成测试 result.xml 文件的目录名称 |
testResultsDir | File(只读) | reportsDir/testReportDirName | 生成测试报告的目录 |
libsDirName | String | libs | 生成 lib 库的目录名称 |
libsDir | File(只读) | buildDir/libsDirName | 生成 lib 库的目录 |
distsDirName | String | distributions | 生成发布文件的目录名称 |
distsDir | File(只读) | buildDir/distsDirName | 生成发布文件的目录 |
docsDirName | String | docs | 生成帮助文档的目录名称 |
docsDir | File(只读) | buildDir/docsDirName | 生成帮助文档的目录 |
dependencyCacheDirName | String | dependency-cache | 存储缓存资源依赖信息的目录名称 |
dependencyCacheDir | File(只读) | buildDir/dependencyCacheDirName | 存储缓存资源依赖信息的目录 |
其他属性:
属性名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
sourceSets | SourceSetContainer (只读) | Not null | 包含工程的资源集合(source sets.) |
sourceCompatibility | JavaVersion,也可以使用字符串或数字,比如 '1.5' 或者 1.5 | 根据使用的 JVM 定 | 编译 java 文件时指定使用的 java 版本 |
targetCompatibility | JavaVersion,也可以使用字符串或数字,比如 '1.5' 或者 1.5 | sourceCompatibility | 生成 classes 的 java 版本 |
archivesBaseName | String | projectName | 作为归档文件的默认名称,如 JAR 或者 ZIP 文件的名称 |
# Java 插件分析
参考官网 (opens new window),以 Java 插件为例,讲解需要关注的几点:
- 我们要关注插件使用
plugins {
id 'java'
}
2
3
我们要关注插件的功能
我们可通过官方文档介绍了解某个插件功能或者百度、再或者大家可以通过 gradle tasks 查看加入 java 插件前后的区别。项目布局
一般加入一个插件之后,插件也会提供相应的目录结构,例如:java 插件的目录结构。
当然这个默认的目录结构也是可以改动的例如:
sourceSets {
main {
java {
srcDirs = ['src/java']
}
resources {
srcDirs = ['src/resources']
}
}
}
2
3
4
5
6
7
8
9
10
也可设置源集的属性等信息。
- 依赖管理:以 java 插件为例,提供了很多依赖管理项
- 额外的属性和方法:
可参考官方文档: sourceCompatibility (JavaVersion.VERSION_1_8)
# Build.gradle 文件
- build.gradle 是一个 gradle 的构建脚本文件,支持 java、groovy 等语言。
- 每个 project 都会有一个 build.gradle 文件,该文件是项目构建的入口,可配置版本、插件、依赖库等信息。
- 每个 build 文件都有一个对应的 Project 实例,对 build.gradle 文件配置,本质就是设置 Project 实例的属性和方法。
- 由于每个 project 都会有一个 build 文件,那么 Root Project 也不列外。Root Project 可以获取到所有 Child Project,所以在 Root Project 的 build 文件中我们可以对 Child Project 统一配置,比如应用的插件、依赖的 maven 中心仓库等。
# 常见属性代码
代码参考:
//指定使用什么版本的JDK语法编译源代码,跟编译环境有关,在有java插件时才能用
sourceCompatibility = 1.8
//指定生成特定于某个JDK版本的class文件:跟运行环境有关,在有java插件时才能用
targetCompatibility = 1.8
//业务编码字符集,注意这是指定源码解码的字符集[编译器]
compileJava.options.encoding "UTF-8"
//测试编码字符集,注意这是指定源码解码的字符集[编译器]
compileTestJava.options.encoding "UTF-8"
//编译JAVA文件时采用UTF-8:注意这是指定源码编码的字符集【源文件】
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
//编译JAVA文件时采用UTF-8:注意这是指定文档编码的字符集【源文件】
tasks.withType(Javadoc) {
options.encoding = "UTF-8"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
提示 1:group+name+version 类似于 maven 的 group+artifactId+version
提示 2:encoding 解决业务代码与测试代码中文乱码问题
# Repositories
repositories {
//gradle中会按着仓库配置的顺序,从上往下依次去对应的仓库中找所需要的jar包:
//如果找到,则停止向下搜索,如果找不到,继续在下面的仓库中查找
//指定去本地某个磁盘目录中查找:使用本地file文件协议:一般不用这种方式
maven { url 'file:///D:/repos/mavenrepos3.5.4'}
maven { url "$rootDir/lib/release" }
//指定去maven的本地仓库查找
mavenLocal()
//指定去maven的私服或者第三方镜像仓库查找
maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
maven { name "Bstek" ; url "https://nexus.bsdn.org/content/groups/public/" }
//指定去maven的远程仓库查找:即 https://repo.maven.apache.org/maven2/
mavenCentral()
//去google仓库查找
google()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
因为 Gradle 没有自己的远程仓库,而是使用 Maven、jcenter、jvy、google 这些远程仓库。
# Subprojects 与 Allprojects
allprojects 是对所有 project (包括 Root Project+ child Project [当前工程和所有子工程]) 的进行统一配置,而 subprojects 是对所有 Child Project 的进行统一配置。
allprojects {
tasks.create('hello') {
doLast { task -> println "project name is $task.project.name"
}
}
}
subprojects {
hello.doLast { task -> println "here is subprojects $task.project.name"
}
}
2
3
4
5
6
7
8
9
10
通常在 subprojects 和 allprojects 中:
allprojects(){ //本质Project中的allprojects方法, 传递一个闭包作为参数。
apply plugin: 'java'
ext {
junitVersion = '4.10'
..
}
task allTask{
...
}
repositories {
...
}
dependencies {
...
}
}
subprojects(){
…//同上面allprojects中的方法。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
拓展 1:如果是直接在根 project 配置 repositories 和 dependencies 则只针对根工程有效。
拓展 2:我们也可以在对单个 Project 进行单独配置:
project('subject01') {
task subject01 {
doLast {
println 'for subject01'
}
}
}
2
3
4
5
6
7
执行 gradle build 指令即可查看测试效果。
# ext 用户自定义属性
Project 和 Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的 ext 属性即可实现。添加之后可以通过 ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过 ext 代码块:
//自定义一个Project的属性
ext.age = 18
//通过代码块同时自定义多个属性
ext {
phone = 15539866006
address = "大吉吧"
}
task extCustomProperty {
//在task中自定义属性
ext {
desc = "食雪"
}
doLast {
println "年龄是:${age}"
println "电话是:${phone}"
println "地址是:${address}"
println "dfd:${desc}"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
测试:通过 gradle extCustomProperty
拓展 1:ext 配置的是用户自定义属性,而 gradle.properties 中一般定义 系统属性、环境变量、项目属性、JVM 相关配置信息。例如
gradle.properties 文件案例:加快构建速度的,gradle.properties 文件中的属性会自动在项目运行时加载。
### 设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出
org.gradle.jvmargs=-Xms4096m -Xmx8192m
### 开启gradle缓存
org.gradle.caching=true
#开启并行编译
org.gradle.parallel=true
#启用新的孵化模式
org.gradle.configureondemand=true
#开启守护进程
org.gradle.daemon=true
2
3
4
5
6
7
8
9
10
详细请参考:Build Environment (gradle.org) (opens new window)
# Buildscript
buildscript 里是 gradle 脚本执行所需依赖,分别是对应的 maven 库和插件。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
}
}
tasks.register('encode') {
doLast {
def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- buildscript {} 必须在 build.gradle 文件的最前端。
- 对于多项目构建,项目的 buildscript () 方法声明的依赖关系可用于其所有子项目的构建脚本。
- 构建脚本依赖可能是 Gradle 插件。
//老式apply插件的引用方式,使用apply+buildscript
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
//此处引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java' //核心插件,无需事先引入
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,才能应用,不必写版本号
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Publishing 项目发布
接下来,将咱们写好的模块发布到公司的私服以供别人使用,如下所示:
# 引入 maven 发布的插件
plugins {
id 'java-library' //如果发布war包, 需要war插件,java-library支持带源码、 文档发布
id 'maven-publish'
}
2
3
4
# 设置发布代码
//带源码和javadoc的发布:需要'java-library'插件支持:它是java的升级版,java插件的功能java-library都有
//javadoc.options.encoding="UTF-8"
//java {
// withJavadocJar()
// withSourcesJar()
//}
publishing {
publications {
myLibrary(MavenPublication) {
groupId = 'org.gradle.sample' //指定GAV坐标信息
artifactId = 'library'
version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories {
//本地仓库位于USER_HOME/.m2/repository
mavenLocal()
//发布项目到私服中
maven {
name = 'myRepo' //name属性可选,表示仓库名称,url必填
//发布地址:可以是本地仓库或者maven私服
//url = layout.buildDirectory.dir("repo")
// change URLs to point to your repos, e.g. http://my.org/repo
def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
//认证信息:用户名和密码
// credentials {
// username = 'joe'
// password = 'secret'
// }
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 执行发布指令
执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:
- generatePomFileForPubNamePublication:生成 pom 文件
- publishPubNamePublicationToRepoNameRepository:发布项目到指定仓库,如果没有仓库名,默认为 maven
- publishPubNamePublicationToMavenLocal:将 PubName 发布复制到本地 Maven 仓库中包括 POM 文件和其他元数据
- publish:发布到 repositories 中指定的仓库 (为比如 Maven 私服)
- publishToMavenLocal:执行所有发布任务中的操作发布到本地 maven 仓库【默认在用户家目录下的.m2/repository】
# 生命周期中 Hook
生命周期中的这些钩子函数都是由 gradle 自动回调完成的,利用这些钩子函数可以帮助我们实现一些我们想要的功能。
Gradle 在生命周期各个阶段都提供了用于回调的钩子函数
- Gradle 初始化阶段
- 在 settings.gradle 执行完后,会回调 Gradle 对象的 settingsEvaluated 方法。
- 在构建所有工程 build.gradle 对应的 Project 对象后,也既初始化阶段完毕,会回调 Gradle 对象的 projectsLoaded 方法。
- Gradle 配置阶段
- Gradle 会循环执行每个工程的 build.gradle 脚本文件
- 在执行当前工程 build.gradle 前,会回调 Gradle 对象的 beforeProject 方法和当前 Project 对象的 beforeEvaluate 方法,虽然 beforeEvalute 属于 project 的生命周期,但是此时 build script 尚未被加载,所以 beforeEvaluate 的设置依然要在 init script 或 setting script 中进行,不要在 build script 中使用 project.beforeEvaluate 方法
- 在执行当前工程 build.gradle 后,会回调 Gradle 对象的 afterProject 方法和当前 Project 对象的 afterEvaluate 方法
- 在所有工程的 build.gradle 执行完毕后,会回调 Gradle 对象的 projectsEvaluated 方法
- 在构建 Task 依赖有向无环图后,也就是配置阶段完毕,会回调 TaskExecutionGraph 对象的 whenReady 方法
- Gradle 执行阶段
- Gradle 会循环执行 Task 及其依赖的 Task
- 在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法
- 在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法
- 当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法
:::notes
Gradle 执行脚本文件的时候会生成对应的实例,主要有如下几种对象:
Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象。
Project 对象:每一个 build.gradle 文件都会转换成一个 Project 对象,类似于 maven 中的 pom.xml 文件。
Settings 对象:settings.gradle 会转变成一个 settings 对象,和整个项目是一对一的关系,一般只用到 include 方法。
Task 对象:从前面的有向无环图中, 我们也可以看出, gradle 最终是基于 Task 的,一个项目可以有一个或者多个 Task。
:::
钩子函数代码演示:项目目录结构如下:
在 root project 的 settings.gradle 文件中添加:
gradle.settingsEvaluated { //1.settingsEvaluated钩子函数,在初始化阶段完成
println "settingsEvaluated"
} gradle.projectsLoaded { //2.projectsLoaded钩子函数,在初始化阶段完成
println "projectsLoaded"
}
//声明一个变量:表示当前项目名,在每次执行某个项目的beforeEvaluate方法时先给projectName变量赋值
//这样方便在:gradle.beforeProject和afterProject两个钩子函数使用。
def projectName=""
gradle.addProjectEvaluationListener(new ProjectEvaluationListener(){
//3.执行各个project的beforeEvaluate:在配置阶段完成
@Override
void beforeEvaluate(Project project) {
projectName=project.name
println "${project.name} Project beforeEvaluate"
}
//5.执行各个project的afterEvaluate:在配置阶段完成
@Override
void afterEvaluate(Project project, ProjectState projectState) {
println "${project.name} Project afterEvaluate"
}
});
gradle.beforeProject {//4.执行各个project的beforeProject:在配置阶段完成
println "${projectName} beforeProject..."
}
gradle.afterProject {//6.执行各个project的afterProject:在配置阶段完成
println "${projectName} afterProject..."
}
//7.所有工程的 build.gradle 执行完毕后,回调 Gradle 对象的 projectsEvaluated 方法:在配置阶段完成
def rootProjectName=rootProject.getName()
gradle.projectsEvaluated {
println "${rootProjectName} projectsEvaluated..."
}
//8.配置阶段完毕后,回调 TaskExecutionGraph 对象的 whenReady 方法:在配置阶段完成
gradle.taskGraph.whenReady {
println "${rootProjectName} taskGraph whenReady..."
}
//9.在当前Task执行之前,会回调 TaskExecutionGraph 对象的 beforeTask方法:在执行阶段完成
gradle.taskGraph.beforeTask {task ->
println "this is the task ${task.name} of the project ${task.getProject().name} beforeTask.."
}
//10.在当前Task执行之后,会回调 TaskExecutionGraph 对象的 afterTask方法:在执行阶段完成
gradle.taskGraph.afterTask {task ->
println "this is the task ${task.name} of the project ${task.getProject().name} afterTask.."
}
//11.当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法:在执行阶段完成
gradle.buildFinished {
println "${rootProjectName} buildFinished..."
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
在 root 的 build.gradle 文件中添加:
task A {
println "root taskA"
doFirst(){
println "root taskA doFirst"
}
doLast(){
println "root taskA doLast"
}
}
2
3
4
5
6
7
8
9
在 subject01 的 build.gradle 文件中添加:
task B {
println "SubProject01 taskB"
doFirst(){
println "SubProject01 taskB doFirst"
}
doLast(){
println "SubProject01 taskB doLast"
}
}
2
3
4
5
6
7
8
9
在 subject02 的 build.gradle 文件中添加:
//task C在上面
task C{
//依赖task D
dependsOn 'D'
println "SubProject02 taskC"
doFirst(){
println "SubProject02 taskC doFirst"
} doLast(){
println "SubProject02 taskC doLast"
}
}
//task D在下面
task D {
println "SubProject02 taskD"
doFirst(){
println "SubProject02 taskD doFirst"
}
doLast(){
println "SubProject02 taskD doLast"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
测试:在 root 工程的根目录执行 gradle C
。就能看到 gradle 生命周期的三个阶段,及每个阶段执行的钩子函数、还有在执行阶段有依赖关系的任务的执行顺序问题。
拓展 1:在 settings.gradle 中添加监听器,查看 task 有向无环图
gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override
//生成有向无环图
void graphPopulated(TaskExecutionGraph taskExecutionGraph) {
taskExecutionGraph.allTasks.forEach(task -> {//核心逻辑:通过taskExecutionGraph获得所有的task
taskExecutionGraph.allTasks.forEach(releaseTask -> {
println "段锋东:" + releaseTask.getProject().name + ":" + releaseTask.name
})
})
}
})
2
3
4
5
6
7
8
9
10
11
测试:在 root 工程根目录下执行 gradle C
。查看测试结果
拓展 2: 计算 Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的 settings 文件,并没有计算 init.gradle 初始化的时间。
projectName = rootProject.getName() //定义项目名
long beginOfSetting = System.currentTimeMillis() //初始化阶段开始时间
def beginOfConfig //配置阶段开始时间
def configHasBegin = false //配置阶段是否开始了,只执行一次
def beginOfProjectConfig = new HashMap() //存放每个 build.gradle 执行之前的时间
def beginOfTaskExecute //执行阶段开始时间
gradle.projectsLoaded { //初始化阶段执行完毕
println "${projectName}工程 初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms"
}
//build.gradle 执行前
gradle.beforeProject { Project project ->
if (!configHasBegin) {
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project, System.currentTimeMillis())
}
//build.gradle 执行后
gradle.afterProject { Project project ->
def begin = beginOfProjectConfig.get(project)
if (project.name == projectName) {
println "根工程${projectName} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
} else {
println "子工程${project.name} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
}
}
gradle.taskGraph.whenReady {//配置阶段完毕
println "整个${projectName}项目在配置阶段总耗时:${System.currentTimeMillis() - beginOfConfig} ms"
beginOfTaskExecute = System.currentTimeMillis()
}
//执行阶段开始
gradle.taskGraph.beforeTask { Task task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println "${task.name}在执行阶段耗时:${System.currentTimeMillis() - task.ext.beginOfTask} ms"
}
}
gradle.buildFinished {//执行阶段完毕
println " 执行阶段总耗时:${System.currentTimeMillis() - beginOfTaskExecute} ms"
println " 整个构建过程耗时:${System.currentTimeMillis() - beginOfSetting} ms"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43