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

Initialization 阶段
主要目的是初始化构建,它又分为两个子过程,一个是执行 Initial 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。
logroot project ├── subproject01 ├── subproject02 └── subproject03内容:里面主要定义了当前 Gradle 项目及子 project 的项目名称。
位置:必须放在根工程目录下。
名字:为
settings.gradle文件,不能发生变化。对应实例:与
org.gradle.api.initialization.Settings实例是一一对应的关系。每个项目只有一个 settings 文件。关注:作为开发者我们只需要关注该文件中的 include 方法即可。使用相对路径【:】引入子工程。
一个子工程只有在 setting 文件中配置了才会被 Gradle 识别,这样在构建的时候才会被包含进去。案例如下所示:
groovy// 根工程项目名 rootProject.name = 'root' // 包含的子工程名称 include 'subproject01' include 'subproject02' include 'subproject03' // 包含的子工程下的子工程名称 include 'subproject01:subproject011' include 'subproject01:subproject012'项目名称中
:代表项目的分隔符,类似路径中的/,如果以:开头则表示相对于 root project。然后 Gradle 会为每个带有 build.gradle 脚本文件的工程构建一个与之对应的 Project 对象。
Tasks
项目实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译 Java 源代码,拷贝文件,打包 Jar 文件,甚至可以是执行一个系统命令。另外,一个 Task 可以读取和设置 Project 的 Property 以完成特定的操作。
任务入门
让我们来先看一个例子:
tasks.register('A') {
println "root taskA"
doFirst() {
println "root taskA doFirst"
}
doLast() {
println "root taskA doLast"
}
}在文件所在的目录执行命令 gradle A。
提示 1:task 的配置段是在配置阶段完成。
提示 2:task 的 doFirst、doLast 方法是在执行阶段完成,并且 doFirst 在 doLast 执行之前执行。
提示 3:区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行。
任务的行为
案例如下:doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义。
tasks.register('A') {
println "root taskA"
doFirst {
println "root taskA doFirst"
}
doLast {
println "root taskA doLast"
}
}
A.doFirst {
println "root taskA doFirst outer"
}
A.doLast {
println "root taskA doLast outer"
}测试 gradle A,输出如下所示:
> Configure project :
root taskA
> Task :A
root taskA doFirst outer
root taskA doFirst
root taskA doLast
root taskA doLast outer底层原理分析:无论是定义任务自身的 action,还是添加的 doLast、doFirst 方法,其实底层都被放入到一个 Action 的 List 中了,最初这个 action List 是空的,当我们设置了 action【任务自身的行为】,它先将 action 添加到列表中,此时列表中只有一个 action,后续执行 doFirst 的时候 doFirst 在 action 前面添加,执行 doLast 的时候在 action 后面添加。doFirst 永远添加在 action List 的第一位,保证添加的 Action 在现有的 action List 最前面;doLast 永远都是在 action List 末尾添加,保证其添加的 Action 在现有的 action List 元素的最后面。一个往前面添加,一个往后面添加,最后这个 action List 就按顺序形成了 doFirst、doSelf、doLast 三部分的 Actions,就达到 doFirst、doSelf、doLast 三部分的 Actions 顺序执行的目的。
任务的依赖方式
Task 之间的依赖关系可以在以下几部分设置
- 参数依赖(已废弃)
- 内部依赖
- 外部依赖
参数依赖
tasks.register('A') {
doLast {
println 'TaskA..'
}
}
tasks.register('B') {
doLast {
println 'TaskB..'
}
}
// 参数方式依赖,已废弃
task('C', dependsOn: ['A', 'B']) {
doLast {
println 'TaskC..'
}
}内部依赖
tasks.register('A') {
doLast {
println 'TaskA..'
}
}
tasks.register('B') {
doLast {
println 'TaskB..'
}
}
tasks.register('C') {
dependsOn('A', 'B')
// dependsOn = ['A', 'B']
doLast {
println 'TaskC..'
}
}外部依赖
tasks.register('A') {
doLast {
println 'TaskA..'
}
}
tasks.register('B') {
doLast {
println 'TaskB..'
}
}
tasks.register('C') {
doLast {
println 'TaskC..'
}
}
C.dependsOn('A', 'B')
// C.dependsOn = ['A', 'B']当然:task 也支持跨项目依赖
在 subproject01 工程的 build.gradle 文件中定义:
tasks.register('A') {
doLast {
println 'TaskA..'
}
}在 subproject02 工程的 build.gradle 文件中定义:
tasks.register('B') {
dependsOn(':subproject01:A') // 依赖根工程下的 subproject01 中的任务A:跨项目依赖
doLast {
println 'TaskB..'
}
}拓展 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 --gradle-version=4.4:gradle wrapper 升级版本号
- 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 可以使用的 worker 数。默认值是处理器数
- -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,--exclude-task:排除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"]:执行自定义任务
拓展:Gradle 任务名可以缩写:任务名支持驼峰式命名风格的任务名缩写,如:connectTask 简写为:cT,执行任务
gradle cT拓展 1:前面提到的 Gradle 指令本质:一个个的 task["任务"],Gradle 中所有操作都是基于任务完成的。
拓展 2:Gradle 默认各指令之间相互的依赖关系:
任务定义方式
任务定义方式,总体分为两大类:一种是通过Project 中的 task() 方法(已废弃),另一种是通过 tasks 对象的 create 或者 register 方法。
task('A', { // 任务名称,闭包都作为参数
println 'task A..'
})
task('B') { // 闭包作为最后一个参数可以直接从括号中拿出来
println 'task B..'
}
task C { // groovy 语法支持省略方法括号
println 'task C..'
}
// 上面三种本质是一种
def map = new HashMap<String, Object>()
map['action'] = { println 'task D..' } // action 属性可以设置为闭包
task(map, 'D')
tasks.create('E') { // 使用 tasks 的 create 方法
println 'task E..'
}
tasks.register('F') { // 注:register 执行的是延迟创建。也即只有当 task 被需要使用的时候才会被创建
println 'task F..'
}当然,我们也可以在定义任务的同时指定任务的属性,具体属性有:
| 配置项 | 描述 | 默认值 |
|---|---|---|
| type | 基于一个存在的 Task 来创建,和我们类继承差不多 | DefaultTask |
| overwrite | 是否替换存在的 Task,这个和 type 配合起来用 | false |
| dependsOn | 用于配置任务的依赖 | [] |
| action | 添加到任务中的一个 Action 或者一个闭包 | null |
| description | 用于配置任务的描述 | null |
| group | 用于配置任务的分组 | null |
在定义任务时也可以给任务分配属性:定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性:

任务类型
前面我们定义的 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.8X、4.X 或 5.X)或 TestNG 测试 |
| Upload | 将 Configuration 的构件上传到一组存储库 |
| War | 组装 WAR 档案 |
| Zip | 组装 ZIP 归档文件。默认是压缩 ZIP 的内容 |
提示 1:如果想看更详细的 Gradle 自带 Task 类型,请参考官方文档:https://docs.gradle.org.cn/current/dsl/index.html#N1052B
提示 2:官方文档在给出这些任务类型的时候,同时给出了案例代码,可以点进去上述官网地址中的某个类型中观看
具体使用例如:
groovytasks.register('myClean', Delete) { delete buildDir }在命令行执行
gradle myClean发现就可以将当前 project 的 build 目录删除
当然除了 Gradle 自带的 Task 类型,我们也可以自定义 Task 类型,如下所示:
自定义 Task 类型
tasks.register('MyDefinitionTask', CustomTask) {
doFirst {
println 'Task 执行之前 执行的 doFirst 方法'
}
doLast {
println 'Task 执行之后 执行的 doLast 方法'
}
}
class CustomTask extends DefaultTask {
// @TaskAction 表示 Task 本身要执行的方法
@TaskAction
def doSelf() {
println "Task 自身在执行的 in doSelf"
}
}测试 gradle MyDefinitionTask
控制台输出
Executing 'MyDefinitionTask'…
> Task :MyDefinitionTask
Task 执行之前 执行的 doFirst 方法
Task 自身在执行的 in doSelf
Task 执行之后 执行的 doLast 方法任务的执行顺序
在 Gradle 中,有三种方式可以指定 Task 执行顺序:
- dependsOn 强依赖方式
- 通过 Task 输入输出
- 通过 API 指定执行顺序
详细请参考官网:https://docs.gradle.org.cn/current/dsl/org.gradle.api.Task.html
动态分配任务
Gradle 的强大功能不仅仅用于定义任务的功能。例如,可以使用它在循环中注册同一类型的多个任务
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "I'm task number $counter"
}
}
}一旦注册了任务,就可以通过 API 访问他们。例如,您可以使用它在运行时动态地向任务添加依赖项。Ant 不允许这样的事情发生。
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "I'm task number $counter"
}
}
}
tasks.named('task0') {
dependsOn('task2', 'task3')
}构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。具体测试如下:
❯ gradle -q task0
I'm task number 2
I'm task number 3
I'm task number 0任务的关闭与开启
每个任务都有一个 enabled 默认为 true 的标志。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。
tasks.register('disableMe') {
doLast {
println 'This task is Executing..'
}
enabled(true) // 直接设置任务开启,默认值为 true
}
disableMe.enabled(false) // 设置关闭任务任务的超时
每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果 --continue 使用,其他任务可以在催场组继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时。
tasks.register('a') {
timeout = Duration.ofMillis(500)
doLast {
Thread.sleep(1000)
println '当前任务 a 执行了'
}
}
tasks.register('b') {
doLast {
println '当前任务 b 执行了'
}
}在控制台使用 gradle a b 测试会发现执行 a 的时候,由于 a 执行超时,抛出异常,所以没有继续往下执行【b 也没执行】。
Details
❯ gradle a b
> Task :a FAILED
Requesting stop of task ':a' as it has exceeded its configured timeout of 500ms.
[Incubating] Problems report is available at: file:///E:/My/rootproject/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':a'.
> Timeout has been exceeded
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to generate a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/9.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1s
1 actionable task: 1 executed然后在控制台使用 gradle a b --continue 测试会发现 a 虽然执行失败,但是 b 还是执行了。
Details
❯ gradle a b --continue
> Task :a FAILED
Requesting stop of task ':a' as it has exceeded its configured timeout of 500ms.
> Task :b
当前任务 b 执行了
[Incubating] Problems report is available at: file:///E:/My/rootproject/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':a'.
> Timeout has been exceeded
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to generate a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/9.2.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1s
2 actionable tasks: 2 executed任务的查找
常用的任务查找方法有:
tasks.register('atguigu') {
doLast {
println '让天下没有难学的技术:尚硅谷'
}
}
// 根据任务名查找
tasks.findByName('atguigu').doFirst {
println '尚硅谷校区 1:北京...'
}
tasks.getByName('atguigu').doFirst {
println '尚硅谷校区 2:深圳...'
}
// 根据任务路径查找【相对路径】
tasks.findByPath(':atguigu').doFirst {
println '尚硅谷校区 3:上海...'
}
tasks.getByPath(':atguigu').doFirst {
println '尚硅谷校区 4:武汉...'
}执行 task gradle atguigu,输出结果如下所示:
Executing 'atguigu'…
> Task :atguigu
尚硅谷校区 4:武汉...
尚硅谷校区 3:上海...
尚硅谷校区 2:深圳...
尚硅谷校区 1:北京...
让天下没有难学的技术:尚硅谷任务的规则
当我们执行、以来一个不存在的任务时,Gradle 会执行失败,报错误信息。那我们能否对其进行改进,当执行一个不存在的任务时,不是报错而是打印提示信息呢?
tasks.register('hello') {
doLast {
println 'hello'
}
}
tasks.addRule('对该规则的一个描述,便于调试、查看等') {
String taskName ->
tasks.register(taskName) {
doLast {
println "该 $taskName 任务不存在,请查证后再执行"
}
}
}测试:使用 gradle abc hello 进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示在昆虫的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况。
任务的 onlyif 断言
断言就是一个条件表达式。Task 有一个 onlyif 方法。它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。具体案例如下所示:
tasks.register('hello') {
doLast {
println 'hello'
}
onlyIf {
!project.hasProperty('fensi')
}
}测试:通过 -P 为 Project 添加 fensi 属性,gradle helo -Pfensi
默认任务
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!'
}
}测试及结果如下:
❯ gradle
> Task :myClean
Default Cleaning!
> Task :myRun
Default Running!Gradle 中的文件操作
集中常见的文件操作方式:
- 本地文件
- 文件集合
- 文件树
- 文件拷贝
- 归档文件
各种文件操作类型的详细介绍如下所示:
本地文件
使用 Project.file(java.lang.Object) 方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前 project['根project或者子project'] 的目录。其实使用 Project.file(java.lang.Object) 方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样。示例代码如下:
// 使用相对路径
def configFile = project.file('src/conf.xml')
configFile.createNewFile();
// 使用绝对路径
configFile = project.file('D:\\conf.xml')
println configFile.createNewFile()
// 使用一个文件对象
configFile = new File('src/config.xml')
println configFile.exists()
tasks.register('myRun') {
doLast {
def f = project.file('.gitignore')
def lines = f.readLines()
lines.forEach { println it}
}
}文件集合
文件集合就是一组文件的列表,在 Gradle 中,文件集合用 FileCollection 接口表示。我们可以使用 Project.files(java.lang.Object) 方法来获得一个文件集合对象,如下代码创建一个 FileCollection 实例:
def collection = project.files(
'src/test1.txt',
new File('src/test2.txt'),
['src/test3.txt', 'src/test4.txt']
)
collection.forEach {
println it.getAbsolutePath()
}
Set set1 = collection.files // 把文件集合转换为 java 中的 Set 类型
Set set2 = collection as Set
List list = collection as List // 把文件集合转换为 java 中的 List 类型
for (final def item in list) {
println item.name
}
def union = collection + project.files('src/test5.txt') // 添加或者删除一个集合
union.forEach {
println it.absolutePath
}对于文件集合我们可以遍历它;也可以把它转换成 java 类型;同时还能使用 + 来添加一个集合,或使用 - 来删除集合。
文件树
文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一个 ZIP 压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。我们可以使用 Project.fileTree(java.util.Map) 方法来创建文件树对象,还可以使用过滤条件来包含或排除相关文件。示例代码如下:
def tree = project.fileTree('src/main').include('**/*.java') // 第一种方式:使用路径创建文件树对象,同时指定包含的文件
// 第二种方式:通过闭包创建文件树
tree = project.fileTree('src/main') {
include('**/*.java')
}
tree = project.fileTree(dir: 'src/main', includes: ['**/*.java']) // 第三种方式:通过路径和闭包创建文件树,具名参数给 map 传值
tree = project.fileTree(dir: 'src/main', includes: ['**/*.java', '**/*.xml', '**/*.txt'], excludes: ['**/test*/**'])
tree.forEach { File file -> // 遍历文件树的所有文件
println file
println file.name
}文件拷贝
我们可以使用 Copy 任务来拷贝文件,通过它可以过滤指定拷贝内容,还能对文件进行重命名操作等。Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用 CopySpec.from(java.lang.Object) 方法指定原文件;使用 CopySpec.to(java.lang.Object) 方法指定目标目录。示例代码如下:
tasks.register('myRun', Copy) {
from 'src/main/resources'
into 'build/config'
}from() 方法接受的参数和文件集合时 files() 一样。当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝);当参数为一个文件时,该文件会被拷贝到指定目录;如果参数指定的文件不存在,就会被忽略;当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。
into() 方法接受的参数与本地文件时 file() 一样。示例代码如下:
tasks.register('myRun', Copy) {
// 拷贝 src/main/webapp 目录下所有的文件
from 'src/main/webapp'
// 拷贝单独的一个文件
from 'src/stagin/index.html'
// 从 Zip 压缩文件中拷贝内容
from project.zipTree('src/main/assets.zip')
// 拷贝到的目标目录
into 'build/explodedWar'
}在拷贝文件的时候还可以添加过滤条件来指定包含或排除的文件,示例如下:
tasks.register('myRun', Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
exclude { details -> details.file.name.endsWith('.html') }
}在拷贝文件的时候还可以对文件进行重命名操作,示例如下:
tasks.register('myRun', Copy) {
from 'src/main/webapp'
into 'build/explodedWar'
// 使用一个闭包方式重命名文件
rename { String fileName -> fileName.replace('-staging-', '') }
}在上面的例子中我们都是使用 Copy 任务来完成拷贝功能的,那么有没有另外一种方式呢?答案是肯定的,那就是 Project.copy(org.gradle.api.Action) 方法。下面示例展示了 copy() 方法的使用方式:
tasks.register('myRun') {
doLast {
copy {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html'
include '**/*.jsp'
}
}
}或者使用 project 对象的 copy 方法:
copy {
// 相对路径或者绝对路径
from project.file('src/main/resources/ddd.txt') // file 也可以换成 new File()
into project.buildDir.absolutePath
}归档文件
通常一个项目会有很多的 Jar 包,我们希望把项目打包成一个 WAR,ZIP 或 TAR 包进行发布,这时我们就可以使用 Zip,Tar,Jar,War 和 Ear 任务来实现,不过它们的用法都一样,所以在这里我们只介绍 Zip 任务的示例。
首先,创建一个 Zip 压缩文件,并指定压缩文件名称。
最后,我们可以使用 Project.zipTree(java.lang.Object) 和 Project.tarTree(java.lang.Object) 方法来创建访问 Zip 压缩包的文件树对象,示例代码如下:
apply plugin: 'java'
version = 1.0
tasks.register('myZip', Zip) {
from 'src/main'
into 'build' // 保存到 zip 包中的 build 目录中
archiveBaseName = 'myGame'
doLast {
def fp = getArchiveFile()
if (fp.isPresent()) {
def rf = fp.get()
println "ZIP file created at: ${rf.getAsFile().absolutePath}"
println "--- Files inside the ZIP ---"
def zt = project.zipTree(rf.getAsFile())
// 这里的 'it' 是一个 FileTreeElement 对象
zt.visit { FileVisitDetails details ->
// details.path 给出的是在归档文件中的相对路径
println details.path
}
}
}
}执行命令 gradle -q myZip,输出结果为:
Executing 'myZip'…
> Task :myZip
ZIP file created at: E:\My\rootproject\build\distributions\myGame-1.0.zip
--- Files inside the ZIP ---
build
build/java
build/java/com
build/java/com/atguigu
build/java/com/atguigu/Main.java
build/resourcesDependencies
依赖的方式
Gradle 中的依赖分为直接依赖、项目依赖、本地 jar 依赖。
案例如下:
dependencies {
// 1. 依赖当前项目下的某个模块['子工程']
implementation project(':subproject01')
// 2. 直接依赖本地的某个 jar 文件
implementation files('libs/foo.jar', 'libs/bar.jar')
// 2. 配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 3. 直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2'
}直接依赖
在项目中直接导入的依赖,就是直接依赖。
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>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>2.17.2</version>
<scope>compile</scope>
</dependency>
</dependencies>项目依赖
从项目的某个模块依赖另一个模块
implementation project(':subproject01')这种依赖方式是直接依赖本工程中的 library module,这个 library module 需要在 setting.gradle 中配置。
本地 jar 依赖
本地 jar 文件依赖,一般包含以下两种方式
// 直接依赖某文件
implementation files('libs/foo.jar', 'libs/bar.jar')
// 配置某文件夹作为依赖项
implementation fileTree(dir:'libs', includes: ['**/*.jar'])依赖的下载
当刷新或执行 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 |
| api | java-library 插件提供支持,这些依赖项可以传递性地导出给使用者,用于编译时和运行时。取代老版本中被溢出的 compile |
| compileOnlyApi | java-library 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要 |
官方文档参考
https://docs.gradle.org.cn/current/userguide/java_library_plugin.html#declarable_bucket_configurations:各个依赖范围的关系和说明
https://docs.gradle.org.cn/current/userguide/java_plugin.html:执行 Java 命令时都使用了哪些依赖范围的依赖
api 与 implement 区别
| api | implemntation | |
|---|---|---|
| 编译时 | 能进行依赖传递,底层变,全部都要变,编译速度慢 | 不能进行依赖传递,底层变,不用全部都要变,编译速度快 |
| 运行时 | 运行时会加载。所有模块的 class 都要被加载 | 运行时会加载,所有模块的 class 都要被加载 |
| 应用场景 | 适用于多模块依赖,避免重复依赖模块 | 多数情况下使用 implementation |
如下所示:
编译时:如果 libC 的内容发生变化,由于使用的是 api 依赖,依赖会传递,所以 libC、libA、projectX 都要发生变化,都需要重新编译,速度慢。
运行时:libC、libA、projectX 中的 class 都要被加载。
编译时:如果 libD 的内容发生变化,由于使用的是 implementation 依赖,依赖不会传递,只有 libD、libB 要变化并重新编译,速度快。
运行时:libC、libA、projectX 中的 class 都要被加载。
拓展 3:api 和 implementation 案例分析
api 的适用场景是多 module 依赖,moduleA 工程依赖了 moduleB,同时 moduleB 又需要依赖了 moduleC,moduleA 工程也需要去依赖 moduleC,这个时候避免重复依赖 module,可以使用 moduleB api 依赖的方式去依赖 moduleC,moduleA 工程只需要依赖 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 platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
}
修改 build.gradle
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
implementation 'org.slf4j:slf4j-api:1.4.0'
}
如上所示:默认下,Gradle 会使用最新版本的 jar 包【考虑到新版本的 jar 包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。当然除此之外,Gradle 也为我们提供了一系列的解决依赖冲突的方法:exclude,移除一个依赖,不允许依赖传递,强制使用某个版本。
Exclude 排除某个依赖
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
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'
}不允许依赖传递
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation('org.hibernate:hibernate-core:3.6.3.Final') {
// 不允许依赖传递,一般不用
transitive false
}
implementation 'org.slf4j:slf4j-api:1.4.0'
}在添加依赖项时,如果设置 transitive 为 false,表示关闭依赖传递。即内部的所有依赖将不会添加到编译和运行时的类路径。
强制使用某个版本
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
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')
}
}
}动态版本声明
表示使用最新版本
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.slf4j:slf4j-api:+'
// implementation 'org.slf4j:slf4j-api:latest.integration'
}Gradle 插件
使用插件的原因
简单地说,通过应用插件我们可以:
- 促进代码重用、减少功能类似代码编写、提升工作效率
- 促进项目更高程度的模块化、自动化、便携化
- 可插拔式的扩展项目的功能
插件的作用
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
- 可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等。
- 可以添加依赖配置到项目中。
- 可以向项目中拓展新的扩展属性、方法等。
- 可以对项目进行一些约定,如应用 Java 插件后,约定
src/main/java目录是我们的源代码存在位置,编译时编译这个目录下的 Java 源代码文件。
插件的分类和使用
脚本插件
脚本插件的本质就是一个脚本文件,使用脚本插件时通过 apply from: 将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如下:
// version.gradle 文件
ext {
company = '尚硅谷'
cfgs = [
compileSdkVersion: JavaVersion.VERSION_1_8
]
spring = [
version: '3.0.0'
]
}下面将在构建文件中使用这个脚本文件,具体如下:
// build.gradle 文件
// map 作为参数
apply from: 'version.gradle'
tasks.register('taskVersion') {
doLast {
println "公司名称为:${company},JDK 版本是${cfgs.compileSdkVersion},版本号是${spring.version}"
}
}上述的代码执行结果如下:
❯ gradle tV
> Task :taskVersion
公司名称为:尚硅谷,JDK 版本是1.8,版本号是3.0.0
BUILD SUCCESSFUL in 1s意义:脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后再主脚本文件引用。
比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
对象插件之内部插件【核心插件】
二进制插件【对象插件】就是实现了 org.gradle.api.Plugin 接口的插件,每个 Java Gradle 插件都有一个 plugin.id。
可通过如下方式使用一个 Java 插件:
apply plugin: 'java' // map 具名参数方式或者:
// 也可以使用**闭包**作为 project.apply 方法的一个参数
apply {
plugin 'java'
}通过上述代码就将 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
// 使用方式 2:org.gradle.api.plugins 默认导入
apply plugin: JavaPlugin
// 使用方式 3:插件的 id,核心插件,无需事先引入
apply plugin: 'java'Gradle 中提供的二进制插件【核心插件】,可参考:https://docs.gradle.org.cn/current/userguide/plugin_reference.html
对象插件之第三方插件
如果是使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径
// 使用传统的应用方式
buildscript {
ext {
springBootVersion = '2.3.3.RELEASE'
}
repositories {
mavenLocal()
maven { url = 'https://maven.aliyun.com/repository/public/' }
jcenter()
}
// 此处先引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
// 再应用插件
apply plugin: 'org.springframework.boot' // 社区插件,需要事先引入,不必写版本号但是如果是第三方插件已经被托管在 https://plugins.gradle.org/ 网站上,就可以不用在 buildscript 里配置 classpath 依赖了,直接使用新出的 plugins DSL 方式引用,案例如下:
使用 plugins DSL 方式
plugins {
id 'org.springframework.boot' version '2.4.1'
}注意:
- 如果使用老式插件方式
buildscript{}要放在 build.gradle 文件的最前面,而新式plugins{}没有该限制。- 托管在网站 gradle 插件官网的第三方插件有两种使用方式,一是传统的 buildscript 方式,一种是 plugins DSL 方式。
对象插件之用户自定义插件
interface GreetingPluginExtension {
Property<String> getMessage()
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// Add a task that uses configuration from the extension object
project.task('hello') {
doLast {
println extension.message.get()
}
}
}
}
apply plugin: GreetingPlugin
// Configure the extension
greeting.message = 'Hi from Gradle'我们直接执行 hello 任务 gradle hello 即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本 Project,而其他的 Project 不能使用。
buildSrc 项目
buildSrc 是 Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。
- 首先先建立一个名为
buildSrc的 Java Module,将 buildSrc 从 included module 移除,重新构建,然后只保留 build.gradle 和 src/main 目录,其它全部删掉,注意名字一定是 buildSrc,不然会找不到插件。 - 然后修改 Gradle 中的内容。
apply plugin: 'groovy' // 必须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() // 必须
implementation localGroovy() // 必须
}
repositories {
google()
mavenCentral() // 必须
}
// 把项目入口设置为 src/main.groovy
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
}
}- 创建入口目录,在
src/main下创建代码入口目录,如下:

- 然后实现插件代码 Text.groovy,注意文件后缀为 groovy,文件要引入
package com.atguigu。
package com.atguigu
import org.gradle.api.Plugin
import org.gradle.api.Project
class Text implements Plugin<Project>{
@Override
void apply(Project project) {
project.tasks.register('atguigu').configure {
doLast {
println '自定义 atguigu 插件'
}
}
}
}接下来在 main 目录下创建 resources 目录,在 resources 目录下创建 META-INF 目录,在 META-INF 目录下创建 gradle-plugins 目录,在 gradle-plugins 目录下创建 properties 文件。
properties 文件可以自己命名,但是要以
.propertis结尾,比如com.atguigu.plugin.properties,其com.atguigu.plugin就是定义的包名路径。

- 最后需要在 properties 文件中指明我们实现插件的全类名
implementation-class=com.atguigu.Text。

到目前为止我们的插件项目已经写完了,在 module 引入我们写的插件 apply plugin: 'com.atguigu.plugin',然后执行插件的 Task,gradle atguigu。
❯ gradle atguigu
> Task :atguigu
自定义 atguigu 插件
BUILD SUCCESSFUL in 867ms这种形式的写法,在我们整个工程的 module 都可以使用,但也只是限制在本工程,其它工程不能使用。
改进:第二种写插件的方式它只能在本工程中使用,而其它的项目工程不能使用,有时候我们需要一个插件在多个工程中使用,这时候我们就需要把插件上传 maven 中。
- 首先将上述 buildSrc 目录复制一份,修改文件夹名,然后在 settings.gradle 文件中使用 include 引入。
- 修改 build.gradle 文件,发布到 maven 仓库中。
apply plugin: 'groovy' // 必须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() // 必须
implementation localGroovy() // 必须
}
repositories {
google()
mavenCentral() // 必须
}
// 把项目入口设置为 src/main.groovy
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
}
}
publishing {
publications {
register('myLibrary', MavenPublication) {
groupId = 'com.atguigu.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'
}
}
}
}- 执行 publish 指令,发布到“根 project 的 lib/release”或者 maven 私服仓库。
- 使用插件,在项目级 build.gradle 文件中将插件添加到 classpath。
buildscript {
repositories {
maven { url = "$rootDir/lib/release" }
}
dependencies {
classpath 'com.atguigu.plugin:library:1.1'
}
}
apply plugin: 'java'
// 是在 atguiguplugin 中定义的插件 ID
apply plugin: 'com.atguigu.plugin'- 执行
gradle atguigu指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了。
最后,至于如何写一个插件,能帮助项目更加自动化或者便携化,是值得大家未来需要长期思考、关注、努力的点。
插件的关注点
插件的引用
apply plugin: '插件名'主要的功能【任务】
当我们在工程中引入插件后,插件会自动的为我们的工程添加一些额外的任务来完成相应的功能。以 Java 插件为例,当我们加入 Java 插件之后,就加入了如下功能:
具体大家可通过 gradle tasks 查看加入某个插件前后的区别。
说明:Gradle 中的任务依赖关系是很重要的,它们之间的依赖关系就形成了构建的基本流程。
工程目录结构
一些插件对工程目录结构有约定,所以我们一般遵循它的约定结构来创建工程,这也是 Gradle 的“约定优于配置”原则。
例如 Java 插件规定的项目源及目录结构如下所示:
src
├── main
│ ├── java
│ └── resources
└── test
├── java
└── resources如果要使用某个插件就应该按照它约定的目录结构设置,这样能大大提高我们的效率,当然各目录结构也可以自己定义。
依赖管理
比如前面我们提到的依赖的类型【“依赖管理”】部分,不同的插件提供了不同的依赖管理。
常用的属性
例如:Java 插件会为工程添加一些常用的属性,我们可以直接在编译脚本中直接使用。
| 属性名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| reportsDirName | String | reports | 生成报告的目录名称 |
| reportsDir | File(只读) | buildDir/reportsDirName | 生成报告的目录 |
| testResultsDirName | String | test-results | 生成测试 result.xml 文件的目录名称 |
| testResultsDir | File(只读) | reportsDir/testReportsDirName | 生成测试报告的目录 |
| 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 版本 |
| archiveBaseName | String | projectName | 作为归档文件的默认名称,如 JAR 或者 ZIP 文件的名称 |
Java 插件分析
参考官网:https://docs.gradle.org.cn/current/userguide/plugin_reference.html,以 Java 插件为例,讲解需要关注的几点:
第一点,我们要关注插件使用。
plugins {
id 'java'
}第二点,我们要关注插件的功能。
我们可以通过官方文档介绍了解某个插件功能或者百度、再或者大家可以通过 gradle tasks 查看加入 java 插件前后的区别。
第三点,项目布局。
一般加入一个插件之后,插件也会提供相应的目录结构,例如:Java 插件的目录结构。
src
├── main
│ ├── java
│ └── resources
└── test
├── java
└── resources当然这个默认的目录结构也是可以改动的,例如:
sourceSets {
main {
java {
srcDirs = ['src/java']
}
resources {
srcDirs = ['src/resources']
}
}
}也可设置源集的属性等信息。
依赖管理:以 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 中心仓库等。
build 文件中常见的属性和方法如下所示:

常见属性代码
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8'
}Repositories
repositories {
mavenLocal()
maven { url = 'https://maven.aliyun.com/repository/public/' }
mavenCentral()
google()
}因为 Gradle 没有自己的远程仓库,而是使用 Maven、jvy、Google 这些远程仓库。
Subprojects 与 Allprojects
allprojects 是对所有 project(包括 Root Project + Child Project[当前工程和所有子工程])进行统一配置,而 subprojects 是对所有 Child Project 进行统一配置。
测试如下:
allprojects {
tasks.register('hello') {
doLast {
println "project name is $it.project.name"
}
}
}
subprojects {
tasks.named('hello').get().doLast {
println "here is subprojects $it.project.name"
}
}通常在 allprojects 和 subprojects 中
allprojects {
apply plugin: 'java'
ext {
junitVersion = '4.10'
// ...
}
tasks.register('allTask') {
// ...
}
repositories {
// ...
}
dependencies {
// ...
}
}
subprojects {
// ...
}拓展 1:如果是直接在根 project 配置 repositories 和 dependencies 则只针对根工程有效。
拓展 2:我们也可以对单个 Project 进行单独配置。
project('subproject01') {
tasks.register('subproject01') {
doLast {
println 'for subproject01'
}
}
}执行 gradle build 指令即可查看测试效果。
ext 用户自定义属性
Project 和 Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的 ext 属性即可实现。添加之后可以通过 ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过 ext 代码块。
// 自定义一个 Project 的属性
ext.age = 18
// 通过代码块同时自定义多个属性
ext {
phone = 12345678901
address = '北京尚硅谷'
}
tasks.register('extCustomProperty') {
// 在 task 中自定义属性
ext {
desc = '奥利给'
}
doLast {
println "年龄是:$age"
println "电话是:$phone"
println "地址是:$address"
println "尚硅谷:$desc"
}
}测试:通过 gradle extCustomProperty
输出结果为:
Executing 'extCustomProperty'…
> Task :extCustomProperty
年龄是:18
电话是:12345678901
地址是:北京尚硅谷
尚硅谷:奥利给
BUILD SUCCESSFUL in 1s拓展 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.configureondmand=true
## 开启守护进程
org.gradle.daemon=true详细请参考:https://docs.gradle.org.cn/current/userguide/build_environment.html#gradle_properties_reference
Buildscript
buildscript 里是 Gradle 脚本执行所需依赖,分别是对应的 maven 库和插件。
案例如下:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'commons-codec:commons-codec:1.2'
}
}
tasks.register('encode') {
doLast {
def encodedString = new org.apache.commons.codec.binary.Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}需要注意的是:
buildscript{}必须在 build.gradle 文件的最前端。- 对于多项目构建,项目的
buildscript{}方法声明的依赖关系可用于其所有子项目的构建脚本。 - 构建脚本依赖可能是 Gradle 插件。
案例如下所示:
// 老式 apply 插件的引用方式,使用 apply+buildscript
buildscript {
ext {
springBootVersion = '2.3.3.RELEASE'
}
repositories {
mavenLocal()
maven { url = 'https://maven.aliyun.com/repository/public/' }
}
// 此处引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
}
}
apply plugin: 'java' // 核心插件,无需事先引入
apply plugin: 'org.springframework.boot' // 社区插件,需要事先引入才能应用,不必写版本号publishing 项目发布
接下来,将咱们写好的模块发布到公司的私服以供别人使用,如下所示:
引入 maven 发布的插件
plugins {
id 'java-library' // 如果发布 war 包,需要 war 插件,java-library 支持带源码、文档发布
id 'maven-publish'
}设置发布代码
// 带源码和 javadoc 的发布:需要 'java-library' 插件支持:它是 java 的升级版,java 插件的功能 java-library 都有
// java {
// javadoc.options.encoding = 'UTF-8'
// withJavadocJar()
// withSourcesJar()
// }
publishing {
publications {
register('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 属性可选,表示仓库名称,urrl 必填
// 发布地址:可以是本地仓库或者 maven 私服
// url = layout.buildDirectory.dir('repos/releases')
// 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.toString().endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
// 认证信息:用户名和密码
credentials {
username = 'joe'
password = 'secret'
}
}
}
}执行发布指令
执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:
- generratePomFileForPubNamePublication:生成 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 方法。
提示:Gradle 执行脚本文件的时候会生成对应的实例,主要有如下几种对象:
- Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象
- Project 对象:每一个 build.gradle 文件都会转换成一个 Project 对象,类似于 maven 中的 pom.xml 文件
- Settings 对象:settings.gradle 会转变成一个 settings 对象,和整个项目是一对一的关系,一般只用到 include 方法
- Task 对象:从前面的有向无环图中,我们也可以看出,Gradle 最终是基于 Task 的,一个项目可以有一个或者多个 Task
拓展 1:钩子函数代码演示
项目目录结构如下:
root project
├── subproject01
├── subproject02
└── subproject03在 root prorject 的 settings.gradle 文件中添加:
gradle.settingsEvaluated { // 1. settingsEvaluated 钩子函数,在初始化阶段完成
println 'settingsEvaluated..'
}
gradle.projectsLoaded { // 2. projectsLoaded 钩子函数,在初始化阶段完成
println 'projectsLoaded..'
}
gradle.addProjectEvaluationListener([
beforeEvaluate: { Project project ->
println "${project.name} Project beforeEvaluate.."
},
afterEvaluate : { Project project, ProjectState state ->
println "${project.name} Project afterEvaluate.."
}
] as ProjectEvaluationListener)
gradle.beforeProject { Project project -> // 4. 执行各个 project 的 beforeProject:在配置阶段完成
println "${project.name} beforeProject.."
}
gradle.afterProject { Project project -> // 6. 执行各个 project 的 afterProject:在配置阶段完成
println "${project.name} afterProject.."
}
// 7. 所有工程的 build.gradle 执行完毕后,回调 Gradle 对象的 projectsEvaluated 方法:在配置阶段完成
gradle.projectsEvaluated {
println "${rootProject.name} projectsEvaluated.."
}
// 8. 配置阶段完毕后,回调 TaskExecutionGraph 对象的 whenReady 方法:在配置阶段完成
gradle.taskGraph.whenReady {
println "${rootProject.name} taskGraph whenReady.."
}
// 9. 在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法:在执行阶段完成
gradle.taskGraph.beforeTask {Task task ->
println "this is the task ${task.name} of the project ${task.project.name} beforeTask.."
}
// 10. 在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法:在执行阶段完成
gradle.taskGraph.afterTask {Task task ->
println "this is the task ${task.name} of the project ${task.project.name} afterTask.."
}
// 11. 当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法:在执行阶段完成
gradle.buildFinished {
println "${rootProject.name} buildFinished.."
}root project 中添加
tasks.register('taskA') {
println('taskA')
doFirst {
println('taskA first..')
}
doLast {
println('taskA last..')
}
}subproject01 中添加
tasks.register('taskB') {
println('taskB')
doFirst {
println('taskB first..')
}
doLast {
println('taskB last..')
}
}subproject02 中添加
tasks.register('taskC') {
println('taskC')
dependsOn 'taskD'
doFirst {
println('taskC first..')
}
doLast {
println('taskC last..')
}
}
tasks.register('taskD') {
println('taskD')
doFirst {
println('taskD first..')
}
doLast {
println('taskD last..')
}
}运行 gradle subproject02:taskC 或 gradle taskC,结果如下
Details
❯ gradle subproject02:taskC
settingsEvaluated..
projectsLoaded..
> Configure project :
rootproject Project beforeEvaluate..
rootproject beforeProject..
rootproject Project afterEvaluate..
rootproject afterProject..
> Configure project :subproject01
subproject01 Project beforeEvaluate..
subproject01 beforeProject..
subproject01 Project afterEvaluate..
subproject01 afterProject..
> Configure project :subproject02
subproject02 Project beforeEvaluate..
subproject02 beforeProject..
subproject02 Project afterEvaluate..
subproject02 afterProject..
> Configure project :subproject03
subproject03 Project beforeEvaluate..
subproject03 beforeProject..
subproject03 Project afterEvaluate..
subproject03 afterProject..
rootproject projectsEvaluated..
taskC
taskD
rootproject taskGraph whenReady..
> Task :subproject02:taskD
this is the task taskD of the project subproject02 beforeTask..
taskD first..
taskD last..
this is the task taskD of the project subproject02 afterTask..
> Task :subproject02:taskC
this is the task taskC of the project subproject02 beforeTask..
taskC first..
taskC last..
this is the task taskC of the project subproject02 afterTask..
rootproject buildFinished..
BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed拓展 2:计算 Gradle 构建过程中各个阶段的耗时
需要注意,这里只是计算了初始化阶段的 settings 文件,并没有计算 init.gradle 初始化的时间。
def projectName = rootProject.name // 定义项目名
def beginOfSetting = System.currentTimeMillis() // 初始化阶段开始时间
long beginOfConfig // 配置阶段开始时间
def configHasBegin = false // 配置阶段是否开始了,只执行一次
def beginOfProjectConfig = [] as HashMap<Project, Long> // 存放每个 build.gradle 执行之前的时间
long beginOfTaskExecute // 执行阶段开始时间
gradle.projectsLoaded {
println "${projectName} 工程 初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms"
}
// build.gradle 执行前
gradle.beforeProject { Project project ->
if (!configHasBegin) {
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig[project] = System.currentTimeMillis()
}
// build.gradle 执行后
gradle.afterProject { Project project ->
def begin = beginOfProjectConfig[project]
println "${project.name == projectName ? '根' : '子'}工程${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"
}创建 Springboot 项目
SpringBoot Gradle 插件为 Gradle 提供 SpringBoot 支持。它允许您打包可执行 jar 或 war 归档文件,运行 SpringBoot 应用程序,并使用 Spring-Boot-dependencies 提供的依赖管理。相关文档请参考 https://docs.spring.io/spring-boot/gradle-plugin/getting-started.html
引入 SpringBoot 插件
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.1'
}
apply plugin: 'io.spring.dependency-management'引入所需要的依赖
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}