30-Gradle 进阶
3、Gradle 进阶
为了让大家快速的入门 gradle,本章将从整体构建脚本的角度介绍:
- 什么是 setting 文件,它有什么作用;
- 说明什么是 build 文件,它又有什么作用
- 我们可以创建多少个 build
- project 和 task,他们有什么作用;又是什么关系,如何配置
- 项目的生命周期
- 项目发布
- 使用 Gradle 创建 SpringBoot 项目等
3.1、项目的生命周期
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 动作】。
3.2、settings 文件
首先对 settings 文件的几点说明:
- 作用:主要是在项目初始化阶段确定一下引入哪些工程需要加入到项目构建中,为构建项目工程树做准备。
- 工程树:gradle 中有工程树的概念,类似于 maven 中的 project 与 module。
- 内容:里面主要定义了当前 gradle 项目及子 project 的项目名称
- 位置:必须放在根工程目录下。
- 名字:
settings.gradle
,不能发生变化 - 对应实例:与
org.gradle.api.initialization.Settings
实例是一一对应的关系。每个项目只有一个 settings 文件。 - 关注:作为开发者我们只需要关注该文件中的 include 方法即可。使用相对路径【
:
】引入子工程。 - 一个子工程只有在 setting 文件中配置了才会被 gradle 识别,这样在构建的时候才会被包含进去。
案例如下所示:
1 |
|
项目名称中 :
代表项目的分隔符,类似路径中的 /
如果以 :
开头则表示相对于 root project 。然后 Gradle 会为每个带有 build.gradle
脚本文件的工程构建一个与之对应的 Project 对象。
3.3、Task
项目实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译 Java 源代码,拷贝文件,打包 Jar 文件,甚至可以是执行一个系统命令。另外,一个 Task 可以读取和设置 Project 的 Property 以完成特定的操作。
3.3.1、任务入门
让我们来先看一个例子:
1 |
|
在文件所在的目录执行命令: gradle A。
- 提示 1 :task 的配置段是在配置阶段完成
- 提示 2 :task 的 doFirst、doLast 方法是执行阶段完成,并且 doFirst 在 doLast 执行之前执行。
- 提示 3 :区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行。
3.3.2、任务的行为
案例如下:doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义
1 |
|
测试:gradle a
,输出如下所示:
底层原理分析:无论是定义任务自身的 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 顺序执行的目的。
其中<<代表 doLast,在 gradle5.x 版本之后就废弃,不能使用了,如下所示:
1 |
|
3.3.3、任务的依赖方式
Task 之间的依赖关系可以在以下几部分设置:
方式一:参数依赖
1 |
|
方式二:内部依赖
1 |
|
方式三:外部依赖
1 |
|
当然:task 也支持跨项目依赖
在 subproject01 工程的 build.gradle 文件中定义:
1 |
|
在 subproject02 工程的 build.gradle 文件中定义:
1 |
|
测试:gradle B,控制台显示如下
拓展 1:当一个 Task 依赖多个 Task 的时候,被依赖的 Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响。
拓展 2:重复依赖的任务只会执行一次,比如:
- A -> B、C
- B -> C
任务 A 依赖任务 B 和任务 C、任务 B 依赖 C 任务。执行任务 A 的时候,显然任务 C 被重复依赖了,C 只会执行一次。
3.3.4、任务执行
任务执行语法: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] : 执行自定义任务 |
- 拓展:gradle 任务名是缩写: 任务名支持驼峰式命名风格的任务名缩写,如:connectTask 简写为:cT,执行任务 gradle cT。
- 拓展 1:前面提到的 Gradle 指令本质:一个个的
task[任务]
,Gradle 中所有操作都是基于任务完成的。 - 拓展 2:gradle 默认各指令之间相互的依赖关系:
相关解释:
3.3.5、任务定义方式
任务定义方式,总体分为两大类:一种是通过 Project 中的 task()
方法,另一种是通过 tasks 对象的 create 或者 register 方法。
1 |
|
当然:我们也可以在定义任务的同时指定任务的属性,具体属性有:
配置项 | 描述 | 默认值 |
---|---|---|
type | 基于一个存在的Task来创建,和我们类继承差不多 | DefaultTask |
overwrite | 是否替换存在的Task,这个和type配合起来用 | false |
dependsOn | 用于配置任务的依赖 | [] |
action | 添加到任务中的一个Action或者一个闭包 | null |
description | 用于配置任务的描述 | null |
group | 用于配置任务的分组 | null |
在定义任务时也可以给任务分配属性:定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性:
1 |
|
可以在 idea 中看到: 上面自定义的那几个任务和 gradle 自带的 clean 任务已经跑到:atguigu 组了。
3.3.6、任务类型
前面我们定义的 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 的内容。 |
- 提示 1:详细的 gradle 自带 Task 类型,参考
- 提示 2: 官方文档在给出这些任务类型的时候,同时给出了案例代码,可以点进去上述官网地址中的某个类型中观看
具体使用例如:
1 |
|
在命令行执行 gradle myClean 发现就可以将当前 project 的 build 目录删除
当然除了 gradle 自带的 task 类型,我们也可以自定义 task 类型,如下所示:
拓展 4: 自定义 Task 类型
1 |
|
测试:gradle MyDefinitionTask
控制台输出:
3.3.7、任务的执行顺序
在 Gradle 中,有三种方式可以指定 Task 执行顺序:
- dependsOn 强依赖方式
- 通过 Task 输入输出
- 通过 API 指定执行顺序
3.3.8、动态分配任务
gradle 的强大功能不仅仅用于定义任务的功能。例如,可以使用它在循环中注册同一类型的多个任务
1 |
|
一旦注册了任务,就可以通过 API 访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。Ant 不允许这样的事情发生。
1 |
|
构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。具体测试如下:
3.3.9、任务的关闭与开启
每个任务都有一个 enabled 默认为的标志 true。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。
1 |
|
3.3.10、任务的超时
每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果 –continue 使用,其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时
1 |
|
在控制台使用: gradle a b 测试会发现执行 a 的时候,由于 a 执行超时,抛出异常,所以没有继续往下执行【b 也没执行】。
然后在控制台使用: gradle a b -continue
,测试会发现 a 虽然执行失败,但是 b 还是执行了。
3.3.11、任务的查找
常用的任务查找方法有:
1 |
|
执行 task:gradle atguigu
,输出结果如下所示:
3.3.12、任务的规则
当我们执行、依赖一个不存在的任务时,Gradle 会执行失败,报错误信息。那我们能否对其进行改进,当执行一个不存在的任务时,不是报错而是打印提示信息呢?
1 |
|
测试: 使用 gradle abc hello
进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况。
3.3.13、任务的 onlyIf 断言
断言就是一个条件表达式。Task 有一个 onlyIf 方法。它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行,否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。具体案例如下所示:
1 |
|
测试:通过 -P
为 Project 添加 fensi 属性: gradle hello -Pfensi
3.3.14、默认任务
Gradle 允许您定义一个或多个在没有指定其他任务时执行的默认任务。
代码如下所示:
1 |
|
测试及结果如下:
3.4、Gradle中的文件操作
几种常见的文件操作方式:
- 本地文件
- 文件集合
- 文件树
- 文件拷贝
- 归档文件
3.4.1、本地文件
使用 Project.file(java.lang.Object)
方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前 project[根 project 或者子 project]
的目录。其实使用 Project.file(java.lang.Object)
方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样。示例代码如下:
1 |
|
3.4.2、文件集合
文件集合就是一组文件的列表,在 Gradle 中,文件集合用 FileCollection 接口表示。我们可以使用 Project.files(java.lang.Object[])
方法来获得一个文件集合对象,如下代码创建一个 FileCollection 实例:
1 |
|
对于文件集合我们可以遍历它;也可以把它转换成 java 类型;同时还能使用+来添加一个集合,或使用-来删除集合。
3.4.3、文件树
文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。我们可以使用 Project.fileTree(java.util.Map)
方法来创建文件树对象,还可以使用过虑条件来包含或排除相关文件。示例代码如下:
1 |
|
3.4.4、文件拷贝
我们可以使用 Copy 任务来拷贝文件,通过它可以过虑指定拷贝内容,还能对文件进行重命名操作等。Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用 CopySpec.from(java.lang.Object[])
方法指定原文件;使用 CopySpec.into(java.lang.Object)
方法指定目标目录。示例代码如下
1 |
|
from()
方法接受的参数和文件集合时 files()
一样。当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝);当参数为一个文件时,该文件会被拷贝到指定目录;如果参数指定的文件不存在,就会被忽略;
当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。
into()
方法接受的参数与本地文件时 file()
一样。示例代码如下
1 |
|
在拷贝文件的时候还可以添加过虑条件来指定包含或排除的文件,示例如下:
1 |
|
在拷贝文件的时候还可以对文件进行重命名操作,示例如下:
1 |
|
在上面的例子中我们都是使用 Copy 任务来完成拷贝功能的,那么有没有另外一种方式呢?答案是肯定的,那就是 Project.copy(org.gradle.api.Action)
方法。下面示例展示了 copy()
方法的使用方式:
1 |
|
或者使用 project 对象的 copy 方法:
1 |
|
执行 gradle build 指令即可。去 build 目录的本地磁盘查看,就能看到。
3.4.5、归档文件
通常一个项目会有很多的 Jar 包,我们希望把项目打包成一个 WAR,ZIP 或 TAR 包进行发布,这时我们就可以使用Zip,Tar,Jar,War 和 Ear 任务来实现,不过它们的用法都一样,所以在这里我只介绍 Zip 任务的示例。
首先,创建一个 Zip 压缩文件,并指定压缩文件名称,如下代码所示:
1 |
|
执行命令 gradle -q myZip,输出结果为:
最后,我们可以使用 Project.zipTree(java.lang.Object)
和 Project.tarTree(java.lang.Object)
方法来创建访问 Zip 压缩包的文件树对象,示例代码如下:
1 |
|
在这里,我们介绍了 Gradle 对本地文件、文件集合、文件树、文件拷贝和归档文件的操作方式。
3.5、Dependencies
3.5.1、依赖的方式
Gradle 中的依赖分别为直接依赖,项目依赖,本地 jar 依赖。
案例如下:
1 |
|
直接依赖:在项目中直接导入的依赖,就是直接依赖
1 |
|
上面是简写法,完整版写法如下:
1 |
|
group/name/version 共同定位一个远程仓库,version 最好写一个固定的版本号,以防构建出问题,implementation 类似 maven 中的依赖的 scope,对比 maven 中的依赖:
1 |
|
项目依赖:从项目的某个模块依赖另一个模块
1 |
|
这种依赖方式是直接依赖本工程中的 libary module,这个 libary module 需要在 setting.gradle 中配置。
本地 jar 依赖:本地 jar 文件依赖,一般包含以下两种方式
1 |
|
3.5.2、依赖的下载
当执行 build 命令时,gradle 就会去配置的依赖仓库中下载对应的 Jar,并应用到项目中。
3.5.3、依赖的类型
类似于 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 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。 |
官方文档参考:
提示 1:java 插件提供的功能,java-library 插件都提供。
3.5.4、api 与 implementation 区别
如下所示:
编译时: 如果 libC 的内容发生变化,由于使用的是 api 依赖,依赖会传递,所以 libC、libA、projectX 都要发生变化,都需要重新编译,速度慢
运行时:libC、libA、projectX 中的 class 都要被加载。
编译时: 如果 libD 的内容发生变化,由于使用的是 implemetation 依赖,依赖不会传递,只有 libD、libB 要变化并重新编译,速度快
运行时:libC、libA、projectX 中的 class 都要被加载。
拓展 3: api 和 implementation 案例分析
api 的适用场景是多 module 依赖,moduleA 工程依赖了 module B,同时 module B 又需要依赖了 module C,modelA 工程也需要去依赖 module C,这个时候避免重复依赖 module,可以使用 module B api 依赖的方式去依赖 module C,modelA 工程只需要依赖 moduleB 即可。
总之,除非涉及到多模块依赖,为了避免重复依赖,咱们会使用 api,其它情况我们优先选择 implementation,拥有大量的 api 依赖项会显著增加构建时间。
3.5.5、依赖冲突及解决方案
依赖冲突是指 “在编译过程中,如果存在某个依赖的多个版本,构建系统应该选择哪个进行构建的问题”,如下所示:
A、B、C 都是本地子项目 module,log4j 是远程依赖。
- 编译时:B 用 1.4.2 版本的 log4j,C 用 2.2.4 版本的 log4j,B 和 C 之间没有冲突
- 打包时:只能有一个版本的代码最终打包进最终的A对应的jar | war包,对于 Gradle 来说这里就有冲突了
案例演示:我们在 build.gradle 引入依赖库
1 |
|
修改 build.gradle
1 |
|
如上所示:默认下,Gradle 会使用最新版本的 jar 包【考虑到新版本的 jar 包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。当然除此之外,Gradle 也为我们提供了一系列的解决依赖冲突的方法: exclude 移除一个依赖,不允许依赖传递,强制使用某个版本。
Exclude 排除某个依赖
1 |
|
不允许依赖传递
1 |
|
在添加依赖项时,如果设置 transitive 为 false,表示关闭依赖传递。即内部的所有依赖将不会添加到编译和运行时的类路径。
强制使用某个版本
1 |
|
拓展:我们可以先查看当前项目中到底有哪些依赖冲突:
1 |
|
3.6、Gradle插件
3.6.1、使用插件的原因
简单的说,通过应用插件我们可以:
- 促进代码重用、减少功能类似代码编写、提升工作效率
- 促进项目更高程度的模块化、自动化、便捷化
- 可插拔式的的扩展项目的功能
3.6.2、插件的作用
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
- 可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等。
- 可以添加依赖配置到项目中。
- 可以向项目中拓展新的扩展属性、方法等。
- 可以对项目进行一些约定,如应用 Java 插件后,约定
src/main/java
目录是我们的源代码存在位置,编译时编译这个目录下的 Java 源代码文件。
3.6.3、插件的分类和使用
第一种:脚本插件
脚本插件的本质就是一个脚本文件,使用脚本插件时通过 apply from:
将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如下:
1 |
|
下面将将在构建文件中使用这个脚本文件,具体如下:
1 |
|
上述代码的执行结果如下:
意义:脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
第二种1:对象插件之内部插件[核心插件]
二进制插件[对象插件]
就是实现了 org.gradle.api.Plugin 接口的插件,每个 Java Gradle 插件都有一个 plugin id。
可通过如下方式使用一个 Java 插件:
1 |
|
通过上述代码就将 Java 插件应用到我们的项目中了,对于 Gradle 自带的核心插件都有唯一的 plugin id,其中 java
是 Java 插件的 plugin id,这个 plugin id 必须是唯一的,可使用应用包名来保证 plugin id 的唯一性。这里的 java 对应的具体类型是 org.gradle.api.plugins.JavaPlugin
,所以可以使用如下方式使用 Java 插件:
1 |
|
Gradle 中提供的二进制插件 核心插件
第二种2:对象插件之第三方插件
如果是使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径
1 |
|
但是如果是第三方插件已经被托管在 https://plugins.gradle.org/ 网站上,就可以不用在 buildscript 里配置 classpath 依赖了,直接使用新出的 plugins DSL 的方式引用,案例如下:
使用 plugins DSL 方式
1 |
|
注意:
- 如果使用老式插件方式
buildscript{}
要放在build.gradle
文件的最前面,而新式plugins{}
没有该限制。 - 托管在网站 gradle 插件官网的第三方插件有两种使用方式,一是传统的 buildscript 方式,一种是 plugins DSL 方式。
第二种3:对象插件之用户自定义插件
1 |
|
我们直接执行 hello 任务 ./gradle hello
即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本 Project,而其他的 Project 不能使用。
3.6.4、buildSrc项目
buildSrc 是 Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。
- 首先先建立一个名为 buildSrc 的 java Module,将 buildSrc 从 included modules 移除,重新构建,然后只保留
build.gradle
和src/main
目录,其他全部删掉,注意名字一定是 buildSrc,不然会找不到插件。 - 然后修改 Gradle 中的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19apply 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'
}
}
} - 创建入口目录,在 src/main 下创建代码入口目录,如下:
- 然后实现插件代码 Text.groovy,注意文件后缀为 groovy,文件要引入 package com.atguigu
1
2
3
4
5
6
7
8
9
10
11
12
13package com.atguigu
import org.gradle.api.Plugin
import org.gradle.api.Project
class Text implements Plugin<Project>{
@Override
void apply(Project project) {
project.task("atguigu"){
doLast{
println("自定义atguigu插件")
}
}
}
} - 接下来在 main 目录下创建 resources 目录,在 resources 目录下创建 META-INF 目录,在 META-INF 目录下创建 gradle-plugins 目录,在 gradle-plugins 目录下创建 properties 文件
- properties 文件可以自己命名,但是要以.properties 结尾,比如 com.atguigu.plugin.properties,其 com.atguigu.plugin 就是定义的包名路径
- 最后需要在 properties 文件中指明我们实现插件的全类名
implementation-class=com.atguigu.Text
到目前为止我们的插件项目已经写完了,在 module 引入我们写的插件 apply plugin:'com.atguigu.plugin'
,然后执行插件的Task,./gradle atguigu
输出:
这种形式的写法,在我们整个工程的 module 都可以使用,但也只是限制在本工程,其他工程不能使用。
改进:第二种写插件的方式他只能在本工程中使用,而其他的项目工程不能使用,有时候我们需要一个插件在多个工程中使用,这时候我们就需要把插件上传 maven 中。
第一步: 首先将上述 buildSrc 目录复制一份,修改文件夹名,然后在 settings.gradle 文件中使用 include 引入
第二步:修改 build.gradle 文件,发布到 maven 仓库中
1 |
|
第三步:执行 publish 指令,发布到根 project 或者 maven 私服仓库。
第四步:使用插件,在项目级 build.gradle 文件中将插件添加到 classpath:
1 |
|
第五步:执行 gradle build 指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了。
最后,至于如何写一个插件,能帮助项目更加自动化或者便捷化,是值得大家未来需要长期思考、关注、努力的点。
3.6.5、插件的关注点
第一点: 插件的引用
1 |
|
第二点: 主要的功能[任务]
当我们在工程中引入插件后,插件会自动的为我们的工程添加一些额外的任务来完成相应的功能。以 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 文件的名称 |
3.6.6、Java插件分析
以 Java 插件为例,讲解需要关注的几点:
第一点:我们要关注插件使用
1 |
|
第二点:我们要关注插件的功能
我们可通过官方文档介绍了解某个插件功能或者百度、再或者大家可以通过 gradle tasks 查看加入 java 插件前后的区别。
第三点:项目布局
一般加入一个插件之后,插件也会提供相应的目录结构,例如:java 插件的目录结构
当然这个默认的目录结构也是可以改动的例如:
1 |
|
也可设置源集的属性等信息。
第四点:依赖管理:以 java 插件为例,提供了很多依赖管理项
第五点:额外的属性和方法
3.7、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 文件中常见的属性和方法如下所示:
3.7.1、常见属性代码
1 |
|
- 提示 1:group+name+version 类似于 maven 的 group+artifactId+version
- 提示 2:encoding 解决业务代码与测试代码中文乱码问题
3.7.2、Repositories
1 |
|
因为 Gradle 没有自己的远程仓库,而是使用 Maven、jcenter、jvy、google 这些远程仓库。
3.7.3、Subprojects与Allprojects
allprojects 是对所有 project(包括 Root Project + child Project[当前工程和所有子工程]
)的进行统一配置,而 subprojects 是对所有 Child Project 的进行统一配置。
测试如下:
1 |
|
通常在 subprojects 和 allprojects 中:
1 |
|
拓展 1: 如果是直接在根 project 配置 repositories 和 dependencies 则只针对根工程有效。
拓展 2: 我们也可以在对单个 Project 进行单独配置:
1 |
|
执行 gradle build 指令即可查看测试效果。
3.7.4、ext用户自定义属性
Project 和 Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的 ext 属性即可实现。添加之后可以通过 ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过 ext 代码块:
1 |
|
测试:通过 gradle extCustomProperty
输出结果为:
拓展 1: ext 配置的是用户自定义属性,而 gradle.properties 中一般定义 系统属性、环境变量、项目属性、JVM 相关配置信息。例如 gradle.properties 文件案例:加快构建速度的,gradle.properties 文件中的属性会自动在项目运行时加载。
1 |
|
3.7.5、Buildscript
buildscript 里是 gradle 脚本执行所需依赖,分别是对应的 maven 库和插件。
案例如下:
1 |
|
需要注意的是:
buildscript{}
必须在 build.gradle 文件的最前端。- 对于多项目构建,项目的 buildscript ()方法声明的依赖关系可用于其所有子项目的构建脚本。
- 构建脚本依赖可能是 Gradle 插件。
案例如下所示:
1 |
|
3.8、publishing 项目发布
接下来,将咱们写好的模块发布发布到公司的私服以供别人使用,如下所示:
3.8.1、引入maven发布的插件
1 |
|
3.8.2、设置发布代码
1 |
|
3.8.3、执行发布指令
执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:
generatePomFileForPubNamePublication
: 生成 pom 文件publishPubNamePublicationToRepoNameRepository
: 发布项目到指定仓库,如果没有仓库名,默认为 mavenpublishPubNamePublicationToMavenLocal
: 将 PubName 发布复制到本地 Maven 仓库中包括 POM 文件和其他元数据。publish
: 发布到 repositories 中指定的仓库(为比如 Maven 私服)publishToMavenLocal
: 执行所有发布任务中的操作发布到本地 maven 仓库【默认在用户家目录下的.m2/repository】。
3.9、生命周期中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
钩子函数代码演示:项目目录结构如下:
在 root project 的 settings.gradle 文件中添加:
1 |
|
在 root 的 build.gradle 文件中添加:
1 |
|
在 subject01 的 build.gradle 文件中添加:
1 |
|
在 subject02 的 build.gradle 文件中添加:
1 |
|
测试:在 root 工程的根目录执行:gradle C .就能看到 gradle 生命周期的三个阶段,及每个阶段执行的钩子函数、还有在执行阶段有依赖关系的任务的执行顺序问题。
拓展 1:在 settings.gradle 中添加监听器,查看 task 有向无环图:
1 |
|
测试:在 root 工程根目录下执行:gradle C。查看测试结果:
拓展 2: 计算 Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的 settings 文件,并没有计算 init.gradle 初始化的时间。
1 |
|
3.10、创建Springboot项目
Spring Boot Gradle 插件在 Gradle 提供 Spring Boot 支持。它允许您打包可执行 jar 或 war 归档文件,运行 Spring Boot 应用程序,并使用 Spring-Boot-dependencies 提供的依赖管理。 参考
3.10.1、引入springboot插件
该插件发布在 Gradle 的插件门户网站上,可以使用插件块来应用:
1 |
|
3.10.2、引入所需要的依赖
1 |
|
3.10.3、执行gradle bootRun指令
要想运行当前 Springboot 项目,直接执行 gradle bootRun 指令或者 idea 右侧按钮即可。
当然如果想让当前项目打成可执行 jar 包,只需执行: gradle bootJar 指令即可。
Cloud 项目创建也可以借助于脚手架创建,与 Boot 项目类似。
3.10.4、拓展spring-boot-gradle-plugin插件
1 |
|
3.11、基于ssm多模块项目案例
3.11.1、多项目模块划分
- meinian-mobile-web: 美年旅游项目的用户系统
- meinian-web: 美年旅游项目的管理员系统
- meinian-service: 美年旅游项目的业务逻辑层
- meinian-dao: 美年旅游项目的持久化层
- meinian-bean: 美年旅游项目的 Model 封装
3.11.2、项目搭建前配置分析
3.11.3、代码演示
代码和配置文件同单体 ssm 一样。只不过做了拆分。
3.11.4、settings.gradle文件中
1 |
|
3.11.5、在根工程build.gradle文件中抽取子模块的公共配置
1 |
|
3.11.6、在根工程的build.gradle文件中配置各个模块的依赖信息
1 |
|
抽取之后,各子模块的 build.gradle 文件就不用配置了。
3.12、微服务实战
3.12.1、创建数据库及表
创建对应的数据库:CREATE DATABASE micro_user
1 |
|
创建对应的数据库:CREATE DATABASE micro_order
1 |
|
3.12.2、搭建项目架构
- microservice-parent:统一管理所有模块的 jar 包版本信息
- microservice-bean: 统一管理所有模块的用到的 pojo 类
- microservice-common:统一管理所有模块的用到的工具类、枚举类、异常处理、日志文件、统一返回结果信息
- microservice-service:统一封装所有的微服务
- microservice-gateway:封装网关信息
3.12.3、具体演示
创建项目结构,记得将 gradle 改成本地自己安装的 gradle。详情请参考视频。