Java高级技术之Gradle
一、Gradle入门
1、简介
Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具,支持 Maven,JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件,转而使用简洁的、支持多种语言(例如:java、groovy 等)的 build 脚本文件;
官网地址: https://gradle.org/
作为Java开发程序员,如果想下载Spring、SpringBoot等Spring家族的源码,基本上基于Gradle构建的,所以虽然目前市面上常见的项目构建工具有Ant、Maven、Gradle,主流还是Maven,但是未来趋势Gradle。
2、常见的项目构建工具
-
Ant
2000 年 Apache 推出的纯Java 编写构建工具,通过 xml[build.xml]文件管理项目。优点:使用灵活,速度快(快于 gradle 和 maven);缺点:Ant 没有强加任何编码约定的项目目录结构,开发人员需编写繁杂XML 文件构建指令,对开发人员是一个挑战。
-
Maven
2004 年Apache 组织推出的再次使用xml 文件[pom.xml]管理项目的构建工具。 优点: 遵循一套约定大于配置的项目目录结构,使用统一的GAV 坐标进行依赖管理,侧重于包管理。缺点:项目构建过程僵化,配置文件编写不够灵活、不方便自定义组件,构建速度慢于 gradle。
-
Gradle
2012 年Google 推出的基于Groovy 语言的全新项目构建工具,集合了Ant 和 Maven 各自的优势。
优点:集 Ant 脚本的灵活性+Maven 约定大于配置的项目目录优势,支持多种远程仓库和插件,侧重于大项目构建。缺点:学习成本高、资料少、脚本灵活、版本兼容性差等。
3、Gradle安装
3.1 安装说明
Gradle官网:https://gradle.org/Gradle
官方下载安装教程页面:https://gradle.org/install/
Gradle官方用户手册:https://docs.gradle.org/current/userguide/userguide.html
SpringBoot 官方文档明确指出,目前SpringBoot 的 Gradle 插件需要gradle6.8 版本及以上,所以我们这里选择 7.x 版本;但其中SpringBoot 与Gradle 存在版本兼容问题,Gradle 与Idea 也存在兼容问题,所以考虑到 java 程序员会使用SpringBoot,所以要选择 6.8 版本及高于 6.8 版本的Gradle,那么相应的idea 版本也要升级。一般去查看{IDEA安装根目录}\plugins\gradle\lib\
查看对应gradle版本
具体参考文档:https://docs.spring.io/spring-boot/docs/2.5.0/gradle-plugin/reference/htmlsingle/#getting-started
3.2 JDK和Gradle安装
安装参考:Gradle 安装配置详解
要求Jdk 为 1.8 或者 1.8 版本以上,并配置好环境变量;Gradle去下载页进行对应版本下载,同时和maven一样配置好环境变量,gradle -v
或者 gradle --version
检测是否安装成功
特别注意:这里我们接着再配置一个GRADLE_USER_HOME 环境变量:
GRADLE_USER_HOME
相当于配置Gradle 本地仓库位置和 Gradle Wrapper 缓存目录。 Gradle本地仓库可以和Maven本地仓库目录一致
4、Gradle 项目目录结构
Gradle 项目默认目录结构和Maven 项目的目录结构一致,都是基于约定大于配置【Convention Over Configuration】
-
只有war工程才有webapp目录,对于普通的jar工程并没有webapp目录
-
gradlew与gradlew.bat执行的指定wrapper版本中的gradle指令,不是本地安装的gradle指令(可以删除)
5、Gradle 创建项目
5.1 常用指令
gradle 的指令要在含有
build.gradle
的目录执行
-
gradle clean:清空build目录
-
gradle classes:编译业务代码和配置文件
-
gradle test:编译测试代码,生成测试报告
-
gradle build:构建项目
-
gradle build -x test:跳过测试构建
5.2 maven源修改
我们可以在gradle 的init.d 目录下创建以.gradle 结尾的文件,.gradle 文件可以实现在build 开始之前执行,这里创建init.gradle文件
1 | allprojects { |
启用init.gradle 文件的方法
-
在命令行指定文件,例如:
gradle --init-script yourdir/init.gradle -q taskName
。你可以多次输入此命令来指定多个init文件 -
把
init.gradle
文件放到USER_HOME/.gradle/
目录下 -
把以.gradle结尾的文件放到
USER_HOME/.gradle/init.d/
目录下 -
把以.gradle结尾的文件放到
GRADLE_HOME/init.d/
目录下
如果存在上面的4种方式的2种以上,gradle会按上面的1-4序号依次执行这些文件,如果给定目录下存在多个init脚本,会按拼音a-z顺序执行这些脚本,每个init脚本都存在一个对应的gradle实例,你在这个文件中调用的所有方法和属性,都会委托给这个gradle实例,每个init脚本都实现了Script接口
仓库地址说明
-
mavenLocal()
: 指定使用maven本地仓库,而本地仓库在配置maven时settings文件指定的仓库位置。如E:/repository,gradle 查找jar包顺序如下:USER_HOME/.m2/settings.xml
>>M2_HOME/conf/settings.xml
>>USER_HOME/.m2/repository
-
maven { url 地址}
,指定maven仓库,一般用私有仓库地址或其它的第三方库【比如阿里镜像仓库地址】 -
mavenCentral()
:这是Maven的中央仓库,无需配置,直接声明就可以使用。jcenter():JCenter中央仓库,实际也是是用的maven搭建的,但相比Maven仓库更友好,通过CDN分发,并且支持https访问,在新版本中已经废弃了,替换为了mavenCentral()。
gradle可以通过指定仓库地址为本地maven仓库地址和远程仓库地址相结合的方式,避免每次都会去远程仓库下载依赖库。这种方式也有一定的问题,如果本地maven仓库有这个依赖,就会从直接加载本地依赖,如果本地仓库没有该依赖,那么还是会从远程下载。但是下载的jar不是存储在本地maven仓库中,而是放在自己的缓存目录中,默认在USER_HOME/.gradle/caches
目录,当然如果我们配置过GRADLE_USER_HOME
环境变量,则会放在GRADLE_USER_HOME/caches
目录,不可以将gradle caches指向maven repository。
阿里云仓库地址请参考:https://developer.aliyun.com/mvn/guide
5.3 Wrapper 包装器
Gradle Wrapper 实际上就是对 Gradle 的一层包装,用于解决实际开发中可能会遇到的不同的项目需要不同版本的 Gradle。
例如把自己的代码共享给其他人使用,可能出现如下情况: 对方电脑没有安装 gradle;对方电脑安装过 gradle,但是版本太旧了。这时候,我们就可以考虑使用 Gradle Wrapper 了。这也是官方建议使用 Gradle Wrapper 的原因。
实际上有了 Gradle Wrapper 之后,我们本地是可以不配置 Gradle 的,下载Gradle 项目后,使用 gradle 项目自带的wrapper 操作也是可以的。 项目中的gradlew、gradlew.cmd脚本用的就是wrapper中规定的gradle版本。
而我们上面提到的gradle指令用的是本地gradle,所以gradle指令和gradlew指令所使用的gradle版本有可能是不一样的。gradlew、gradlew.cmd的使用方式与gradle使用方式完全一致,只不过把gradle指令换成了gradlew指令。 我们也可在终端执行 gradlew 指令时,指定指定一些参数,来控制 Wrapper 的生成,比如依赖的版本等
-
—gradle-version
:指定使用的Gradle版本; -
—gradle-distribution-url
:指定下载Gradle发行版的url地址 -
gradle wrapper --gradle-version=4.4
:升级wrapper版本号,只是修改gradle.properties中wrapper版本,未实际下载 -
gradle wrapper --gradle-version 5.2.1 --distribution-type all
:关联源码用
**GradleWrapper 的执行流程 **
-
当我们第一次执行
./gradlew build
命令的时候,gradlew 会读取gradle-wrapper.properties
文件的配置信息 -
准确的将指定版本的 gradle 下载并解压到指定的位置(GRADLE_USER_HOME目录下的wrapper/dists目录中)
-
构建本地缓存(GRADLE_USER_HOME目录下的caches目录中),下载再使用相同版本的gradle就不用下载了
-
之后执行的
./gradlew
所有命令都是使用指定的 gradle 版本
1 | # 下载的Gradle压缩包解压后存储的主目录 |
注意:前面提到的 GRALE_USER_HOME
环境变量用于这里的Gradle Wrapper 下载的特定版本的gradle 存储目录。如果我们没有配置过GRALE_USER_HOME
环境变量,默认在当前用户家目录下的.gradle 文件夹中
二、Gradle 与 Idea 整合
1、Groovy简介
在某种程度上,Groovy 可以被视为Java 的一种脚本化改良版,Groovy 也是运行在 JVM 上,它可以很好地与 Java 代码及其相关库进行交互操作。它是一种成熟的面向对象编程语言,既可以面向对象编程,又可以用作纯粹的脚本语言。
大多数有效的 Java 代码也可以转换为有效的 Groovy 代码,Groovy 和 Java 语言的主要区别是:完成同样的任务所需的Groovy 代码比 Java 代码更少。其特点为:
-
功能强大,例如提供了动态类型转换、闭包和元编程(metaprogramming)支持
-
支持函数式编程,不需要main 函数
-
默认导入常用的包
-
类不支持 default 作用域,且默认作用域为public。
-
Groovy 中基本类型也是对象,可以直接调用对象的方法。
-
支持DSL(Domain Specific Languages 领域特定语言)和其它简洁的语法,让代码变得易于阅读和维护。
-
Groovy 是基于Java 语言的,所以完全兼容Java 语法,所以对于java 程序员学习成本较低。详细了解请参考:http://www.groovy-lang.org/documentation.html
2、Groovy 安装[非必须]和项目创建
安装完后配置好环境变量,创建项目时选择Groovy项目,同时指定对应的Groovy SDK
3、Groovy基本类型
-
类型转换:当需要时,类型之间会自动发生类型转换: 字符串(String)、基本类型(如int) 和类型的包装类 (如Integer)
-
类说明:如果在一个groovy 文件中没有任何类定义,它将被当做 script 来处理,也就意味着这个文件将被透明的转换为一个 Script 类型的类,这个自动转换得到的类将使用原始的 groovy 文件名作为类的名字。groovy 文件的内容被打包进run 方法,另外在新产生的类中被加入一个main 方法以进行外部执行该脚本。
3.1 基本注意点
方法调用时,在不含有歧义的地方可以省略方法调用时的括号。这类似于使用${变量名}
时,括号在不引起歧义的地方可以省略是一样的
1 | def num1=1; |
3.2 引号说明
1 | def num1=1; |
3.3 三个语句结构
Groovy 支持顺序结构从上向下依次解析、分支结构(if…else、if…else if …else…、switch…case、for、while、do…while)
具体参考官网:http://www.groovy-lang.org/semantics.html#_conditional_structures
3.4 类型及权限修饰符
-
原生数据类型及包装类
-
类、内部类、抽象类、接口
-
注解
-
Trait: 可以看成是带有方法实现的接口
Groovy 中 各 种 各 样 的 数 据 类 型 和 权 限 修 饰 符 及 Goovy 与 Java 区 别 请 参 考 : http://www.groovy-lang.org/objectorientation.html#_modifiers_on_a_property
Groovy 类与 Java 类之间的主要区别是:
-
没有可见性修饰符的类或方法自动是公共的(可以使用一个特殊的注释来实现包的私有可见性)。
-
没有可见性修饰符的字段将自动转换为属性,不需要显式的 getter 和 setter 方法。
-
如果属性声明为 final,则不会生成 setter。
-
一个源文件可能包含一个或多个类(但是如果一个文件不包含类定义的代码,则将其视为脚本)。脚本只是具有一些特殊约定的类,它们的名称与源文件相同(所以不要在脚本中包含与脚本源文件名相同的类定义)。
3.5 集合操作
参考官网:http://www.groovy-lang.org/syntax.html#_number_type_suffixes
Groovy中可以把不同的基本类型添加到同一集合中
-
List:
add()/plus()/remove()/removeElement()/removeAll()/pop()/putAt()/each()/size()/contains()
-
Map:
put()/remove()/+、-/each()
3.6 类导入
Groovy 遵循 Java 允许 import 语句解析类引用的概念
1 | import groovy.xml.MarkupBuilder |
Groovy 语言默认提供的导入
1 | import java.lang.* |
3.7 异常处理
参考官网地址: http://www.groovy-lang.org/semantics.html#_try_catch_finally
1 | def z |
3.8 闭包
闭包:Groovy 中的闭包是一个开放的、匿名的代码块,它可以接受参数、也可以有返回值。闭包可以引用其周围作用域中声明的变量。
语法:{ [closureParameters -> ] statements }
其中[ closureParameters-> ]是一个可选的逗号分隔的参数列表,参数后面是 Groovy 语句。参数类似于方法参数列表, 这些参数可以是类型化的,也可以是非类型化的。当指定参数列表时,需要使用-> 字符,用于将参数与闭包体分离。
1 | //闭包体完成变量自增操作 |
闭包调用方式: 闭包是 groovy.lang.Closure 的实例。它可以像任何其他变量一样分配给一个变量或字段。 闭包对象(参数)
;闭包对象.call(参数)
1 | def isOdd = { int i -> i%2 != 0 } |
4、Gradle创建工程
4.1 创建
创建项目时,选择Build System为Gradle,DSL为Groovy;之后修改当前项目使用本地安装的gradle:可以加快下载项目依赖jar 包的速度【配置了私服地址】(在Settings→Build,Execution→Build Tools→Gradle中配置本地)
注意事项
-
在Terminal 中执行以gradlew 开头命令和操作图形化的IDEA 使用Gradle 版本不一定是同一个版本;Terminal中以gradlew开头指令用的是Wrapper规定的gradle版本,wrapper中规定版本默认和idea插件中规定的版本一致;而图形化的IDEA使用Gradle是本地安装的
-
目前只能是在创建项目时重新设置本地gradle,创建新项目需要重新去改
-
我 们 在
gradle.build
文 件 添 加 依 赖 之 后 , 这 些 依 赖 会 在 下 载 到GRADLE_USER_HOME/caches/modules-2/files-2.1
目录下面,所以这里的 GRADLE_USER_HOME 相当于 Gradle 的本地仓库
对于低版本的war包部署,可以参考Gretty 官网地址:http://akhikhl.github.io/gretty-doc/index.html
4.2 测试
测试任务自动检测并执行测试源集中的所有单元测试。测试执行完成后会生成一个报告。支持JUnit 和 TestNG 测试
Gradle 对于Junit4.x 支持
1 | dependencies { |
Gradle 对于Junit5.x 版本支持
1 | dependencies { |
注意:无论是 Junt4.x 版本还是Junit5.x 版本,我们只需在 build.gradle 目录下执行gradle test 指令,gradle 就会帮我们执行所有的加了@Test 注解的测试,并生成测试报告。
gradle 在junit 中的批量测试,可以设置包含或者排除某些特定测试
1 | test { |
三、Gradle进阶学习
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 动作】
2、Settings文件
-
作用:主要是在项目初始化阶段确定一下引入哪些工程需要加入到项目构建中,为构建项目工程树做准备。
-
工程树:gradle 中有工程树的概念,类似于 maven 中的project 与module。
-
内容:里面主要定义了当前 gradle 项目及子 project 的项目名称
-
位置:必须放在根工程目录下。
-
名字:为settings.gradle 文件,不能发生变化
-
对应实例:与 org.gradle.api.initialization.Settings 实例是一一对应的关系。每个项目只有一个settings 文件。
-
关注:作为开发者我们只需要关注该文件中的include 方法即可。使用相对路径【 : 】引入子工程。
-
一个子工程只有在setting 文件中配置了才会被 gradle 识别,这样在构建的时候才会被包含进去
1 | //根工程项目名 |
3、Task
项目实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译Java 源代码,拷贝文件, 打包Jar 文件,甚至可以是执行一个系统命令。另外,一个 Task 可以读取和设置Project 的Property 以完成特定的操作
可参考官方文档:https://docs.gradle.org/current/userguide/tutorial_using_tasks.html
3.1 入门Demo
在build.gradle
编写一下测试,在文件所在的目录执行命令: gradle -i A
。
-
task 的配置段是在配置阶段完成
-
task 的doFirst、doLast 方法是执行阶段完成,并且doFirst 在doLast 执行之前执行。
-
区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行
1 | task A { |
3.2 任务的行为
doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义
1 | def map=new HashMap<String,Object>(); |
底层原理分析
无论是定义任务自身的 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 版本之后就废弃
3.3 任务的依赖
1 | // 参数方式依赖 |
-
当一个 Task 依赖多个Task 的时候,被依赖的Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响
-
重复依赖的任务只会执行一次
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) |
守护进程选项,也可以配置在gradle.properties中 | –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 指令本质:一个个的task[任务],Gradle 中所有操作都是基于任务完成的。
3.5 任务定义方式
任务定义方式,总体分为两大类:一种是通过 Project 中的task()方法,另一种是通过tasks 对象的 create 或者register 方法
1 | task('A', {//任务名称,闭包都作为参数println "taskA..." |
我们也可以在定义任务的同时指定任务的属性
-
type:基于一个存在的Task来创建,和继承类似,默认值DefaultTask
-
overwrite:是否替换存在的Task,这个和type配合使用,默认值false
-
dependsOn:用户配置任务的依赖,默认值[]
-
action:添加到任务中的一个Action或者一个闭包,默认值null
-
description:用于配置任务的描述,默认值null
-
group:用于配置任务的分组,默认值null
在定义任务时也可以给任务分配属性:定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性
1 | //①.F是任务名,前面通过具名参数给map的属性赋值,以参数方式指定任务的属性信息 |
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 的内容 |
举例在命令行执行 gradle myClean 发现就可以将当前project 的 build 目录删除
1 | tasks.register('myClean', Delete) { |
自定义 Task 类型
1 | def myTask = task MyDefinitionTask(type: CustomTask) |
3.7 任务的执行顺序与动态分配
官网:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
在 Gradle 中, 有三种方式可以指定 Task 执行顺序:
-
dependsOn 强依赖方式
-
通过 Task 输入输出
-
通过 API 指定执行顺序
gradle可以使用它在循环中注册同一类型的多个任务
1 | // 构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。 |
3.8 任务的关闭、开启与超时
每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果 --continue 使用,其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时
1 | //每个任务都有一个 enabled 默认为的标志 true。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。 |
3.9 任务的查找
1 | task atguigu { |
3.10 任务的规则
使用 gradle abc hello 进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况
1 | task hello { |
3.11 任务的 onlyIf 断言
断言就是一个条件表达式。Task 有一个 onlyIf 方法。它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行, 否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等
1 | task hello { |
测试:通过-P 为Project 添加fensi 属性 gradle hello -Pfensi
3.12 默认任务
1 | defaultTasks 'myClean', 'myRun' |
4、Gradle 中的文件操作
4.1 本地文件
使用 Project.file(java.lang.Object)
方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前project**[根project 或者子project]**的目录。其实使用 Project.file(java.lang.Object)
方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样
1 | //使用相对路径 |
4.2 文件集合
文 件 集 合 就 是 一 组 文 件 的 列 表 , 在 Gradle 中 , 文 件 集 合 用 FileCollection 接 口 表 示 ,我 们 可 以 使 用 Project.files(java.lang.Object[])
方法来获得一个文件集合对象
1 | def collection = files('src/test1.txt', new File('src/test2.txt'), ['src/test3.txt', 'src/test4.txt']) |
4.3 文件树
文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。我们可以使用 Project.fileTree(java.util.Map)
方法来创建文件树对象, 还可以使用过虑条件来包含或排除相关文件
1 | def tree = fileTree(dir: 'src/main', include: '**/*.java') |
4.4 文件拷贝
我们可以使用 Copy 任务来拷贝文件,通过它可以过虑指定拷贝内容,还能对文件进行重命名操作等。Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用CopySpec.from(java.lang.Object[])
方法指定原文件;使用CopySpec.into(java.lang.Object)
方法指定目标目录
1 | task copyTask(type: Copy) { |
from()方法接受的参数和文件集合时files()一样。当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝);当参数为一个文件时,该文件会被拷贝到指定目录;如果参数指定的文件不存在,就会被忽略; 当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。
1 | task copyTask(type: Copy) { |
4.5 文件归档
官方文档:https://docs.gradle.org/current/userguide/working_with_files.html
通常一个项目会有很多的Jar 包,我们希望把项目打包成一个WAR,ZIP 或TAR 包进行发布,这时我们就可以使用Zip,Tar,Jar,War 和Ear 任务来实现,不过它们的用法都一样,所以在这里我只介绍Zip 任务的示例。
1 | apply plugin: 'java' |
5、Dependencies
5.1 依赖方式
Gradle 中的依赖分别为直接依赖,项目依赖,本地jar 依赖
1 | dependencies { |
当执行 build 命令时,gradle 就会去配置的依赖仓库中下载对应的 Jar,并应用到项目中
5.2 依赖类型
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin: 各个依赖范围的关系和说明
https://docs.gradle.org/current/userguide/upgrading_version_6.html#sec:configuration_removal : 依赖范围升级和移除
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin:API 和implemention 区别
https://docs.gradle.org/current/userguide/java_plugin.html#java_plugin: 执行java 命令时都使用了哪些依赖范围的依
类似于 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 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。 |
5.3 api 与implementation 区别
api | implementation | |
---|---|---|
编译时 | 能进行依赖传递,底层变,全部都要变、编译速度慢 | 不能进行依赖传递,底层变不用全部都要变、编译速度快 |
运行时 | 运行时会加载,所有模块的class都要被加载 | 运行时会加载,所有模块的class都要被加载 |
应用场景 | 适用于多模块依赖,避免重复依赖模块 | 多数情况下使用implementation |
api 的适用场景是多module 依赖,moduleA 工程依赖了 module B,同时module B 又需要依赖了 module C,modelA 工程也需要去依赖 module C;这个时候避免重复依赖module,可以使用 module B api 依赖的方式去依赖module C,modelA 工程只需要依赖 moduleB 即可。
总之,除非涉及到多模块依赖,为了避免重复依赖,咱们会使用api,其它情况我们优先选择implementation,拥有大量的api 依赖项会显著增加构建时间。
5.4 依赖冲突及解决方案
依赖冲突是指"在编译过程中, 如果存在某个依赖的多个版本, 构建系统应该选择哪个进行构建的问题"
默认下,Gradle 会使用最新版本的jar 包【考虑到新版本的jar 包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。当然除此之外,Gradle 也为我们提供了一系列的解决依赖冲突的方法: exclude移除一个依赖,不允许依赖传递,强制使用某个版本
1 | //Exclude 排除某个依赖 |
6、Gradle 插件
6.1 插件介绍
-
促进代码重用、减少功能类似代码编写、提升工作效率
-
促进项目更高程度的模块化、自动化、便捷化
-
可插拔式的的扩展项目的功能
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
-
可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等
-
可以添加依赖配置到项目中。
-
可以向项目中拓展新的扩展属性、方法等。
-
可以对项目进行一些约定,如应用Java 插件后,约定src/main/java 目录是我们的源代码存在位置,编译时编译这个目录下的Java 源代码文件
6.2 插件的分类和使用
脚本插件
脚本插件的本质就是一个脚本文件,使用脚本插件时通过apply from:将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用。
脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等
1 | //version.gradle文件 |
对象插件之内部插件[核心插件]
https://docs.gradle.org/current/userguide/plugin_reference.html
二进制插件[对象插件]就是实现了org.gradle.api.Plugin 接口的插件,每个Java Gradle 插件都有一个plugin id
1 | // 使用plugins DSL 方式 |
第三方插件
1 | //使用传统的应用方式 |
用户自定义插件
参考地址:https://docs.gradle.org/current/userguide/custom_plugins.html
我们直接执行hello 任务./gradle hello 即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本Project,而其他的Project 不能使用
1 | interface GreetingPluginExtension { |
6.3 buildSrc 项目
buildSrc 是Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。
-
首先先建立一个名为** buildSrc 的 java Module**,将 buildSrc 从 included modules 移除,重新构建,然后只保留 build.gradle和src/main 目录,其他全部删掉,注意名字一定是 buildSrc,不然会找不到插件
-
然后修改
build.gradle
中的内容
1 | apply plugin: 'groovy' //必须 |
然后实现插件代码Text.groovy,注意文件后缀为groovy,文件要引入package com.atguigu
1 | package com.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'
,然后执行插件的任务./gradle atguigu
如果想给其他工程使用,可以上传maven
-
首先将上述buildSrc 目录复制一份,修改文件夹名,然后在settings.gradle 文件中使用include 引入
-
修改build.gradle 文件,发布到maven 仓库中
1 | apply plugin: 'groovy' //必须 |
-
执行publish 指令,发布到根project 或者maven 私服仓库(这里发布到了本地)
-
使用插件,在项目级build.gradle 文件中将插件添加到classpath:
1 | buildscript { |
- 执行
gradle build
指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了
6.4 插件常用属性
参考官网:https://docs.gradle.org/current/userguide/plugin_reference.html
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 中心仓库等。
7.1 常见属性代码
-
group+name+version 类似于 maven 的group+artifactId+version
-
encoding 解决业务代码与测试代码中文乱码问题
1 | //指定使用什么版本的JDK语法编译源代码,跟编译环境有关,在有java插件时才能用 |
7.2 Repositories
Gradle 没有自己的远程仓库,而是使用Maven、jcenter、jvy、google 这些远程仓库
1 | repositories { |
7.3 Subprojects 与 Allprojects
**allprojects 是对所有project(包括Root Project+ child Project[当前工程和所有子工程])的进行统一配置,而subprojects **是对所有Child Project 的进行统一配置
1 | allprojects { |
7.4 ext 用户自定义属性
详细请参考:https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
Project 和Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的ext 属性即可实现。添加之后可以通过ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过ext 代码块
1 | //自定义一个Project的属性 |
ext 配置的是用户自定义属性,而gradle.properties 中一般定义系统属性、环境变量、项目属性、JVM 相关配置信息。例如gradle.properties 文件案例:加快构建速度的,gradle.properties 文件中的属性会自动在项目运行时加载。
1 | ## 设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出 |
7.5 Buildscript
buildscript 里是gradle 脚本执行所需依赖,分别是对应的 maven 库和插件
-
buildscript{}必须在build.gradle 文件的最前端
-
对于多项目构建,项目的buildscript ()方法声明的依赖关系可用于其所有子项目的构建脚本
-
构建脚本依赖可能是Gradle 插件
1 | import org.apache.commons.codec.binary.Base64 |
8、项目发布
1 | plugins { |
执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:
-
generatePomFileForPubNamePublication: 生成pom 文件
-
publishPubNamePublicationToRepoNameRepository:发布项目到指定仓库,如果没有仓库名,默认为maven
-
publishPubNamePublicationToMavenLocal: 将PubName 发布复制到本地Maven 仓库中包括POM 文件和其他元数据。
-
publish: 发布到repositories 中指定的仓库(为比如Maven 私服)
-
publishToMavenLocal: 执行所有发布任务中的操作发布到本地maven 仓库【默认在用户家目录下的.m2/repository】
9、生命周期中Hook
9.1 生命周期详细介绍
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
方法。
1 | gradle.settingsEvaluated { //1.settingsEvaluated钩子函数,在初始化阶段完成 |
9.2 生命周期扩展
在settings.gradle
中添加监听器,查看task 有向无环图
1 | gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() { |
计算Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的settings 文件
,并没有计算init.gradle
初始化的时间
1 | def projectName=rootProject.getName() //定义项目名 |
四、Spring项目相关
1、Springboot 项目创建
Spring Boot Gradle 插件在Gradle 提供Spring Boot 支持。它允许您打包可执行jar 或war 归档文件,运行Spring。参考:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#getting-started
1.1 引入springboot 插件
创建SpringBoot项目,选择gradle和groovy,完成后配置本地gradle,然后进行依赖引入(用Spring Boot脚手架会直接配置好,推荐)
1 | plugins { |
1.2 程序运行与打包
要想运行当前Springboot 项目,直接执行gradle bootRun
指令或者idea 右侧按钮即可
。当然如果想让当前项目打成可执行jar 包,只需执行: gradle bootJar
指令即可。
Cloud 项目创建也可以借助于脚手架创建,与Boot 项目类似。
1.3 拓展spring-boot-gradle-plugin 插件
1 | buildscript { |
1.4 全局属性管理
有时我们在 gradle 里定义了一些全局属性,想在 springboot 的 application 配置文件里使用,甚至可以给多个模块公用一个全局属性
首先在根目录build.gradle
进行配置,首先需要暴露属性
1 | // 将 gradle 的配置应用于yml配置文件 |
然后在根目录创建例如gradle.properties
1 | author=shawn |
最后在项目的application引入即可,banner也可以按此方法引入
1 | info: |
2、Spring聚合项目
参考Maven聚合项目:SpringBoot聚合项目创建、打包与多环境
官方参考:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/
2.1 环境与依赖
本次的微服务结构如下所示,当然也有另一种即消费者和生产者分开单独成一个微服务。创建父工程时选择Spring Initializer
脚手架,选择Gradle类型,填写好对于信息,同时删除src等其他非必须文件,然后进入Settings设置全局gradle配置(每次新建都要配置一下);之后在创建子模块时可以直接选择新建Gradle,选择Java语言以及父目录
-
microservice-parent: 统一管理所有模块的 jar 包版本信息
-
microservice-bean: 统一管理所有模块的用到的 pojo 类
-
microservice-common:统一管理所有模块的用到的工具类、枚举类、异常处理、日志文件、统一返回结果信息
-
microservice-service: 统一封装所有的微服务
-
microservice-gateway: 封装网关信息
2.2 父工程相关目录
首先关注settings.gradle
文件,该文件一个项目只能存在一个,用于配置项目的父子关系
1 | rootProject.name = 'microservice' |
然后在根目录创建version.gradle文件,用户自定义用户属性和版本号
1 | // 依赖版本管理 |
接下来build.gradle
是父工程的全局配置管理中心,一般在这里设置版本号等信息,然后子工程进行继承,这样子工程就不需要写版本号了。
1 | description 'gradle微服务实战父工程' |
2.3 配置公共子模块
创建bean和common模块
1 | // jar包后的名字,根据需要自定义 |
2.4 配置Service服务类
首先创建service模块服务,下面在分别创建order和user服务,因为service亚父工程不需要jar包,所以在build.gradle
配置,同时删除src等其他非必须文件
1 | // 不需要打可执行jar包 |
在子模块的build.gradle中进行配置,此时根据需要进行依赖的引入,不过此时不需要进行版本号的编写,版本号统一由父工程进行管理;下面的文件因为版本依赖等已经由父工程配置了,因此无需额外配置
1 | archivesBaseName = 'order' |
最后如果需要将其他模块的bean加入Spring管理,注意在主启动类添加包扫描路径@ComponentScan("com.shawn")
,最后在对于的目录下进行打包gradle build
,或者IDEA点击bootJar也是打包可执行Jar包(非jar按钮)
参考