无论那种项目构建工具,都有自身的优势和劣势,所以选择一款最适合项目的就是最好的!
Gradle项目默认目录结构和Maven项目的目录结构一致,都是基于约定大于配置【Convention Over Configuratiion】
注意:gradle的指令要在含有build.gradle的目录执行。
Gradle自带的Maven源地址是国外的,该Maven源在国内的访问速度是很慢的,除非使用了特别的手段。一般情况下,我们建议使用国内的第三方开发的Maven源或企业内部自建Maven源。
Gradle Wrapper实际上就是对Gradle的一层包装,用于解决实际开发中可能会遇到的不同的项目需要不同版本的Gradle问题。例如:把自己的代码共享给其他人使用,可能出现如下情况:
1、对方的服务器没有安装gradle
2、对方的服务器安装过gradle,但是版本太旧了
这时候,我们就可以考虑使用Gradle Wrapper了。这也是官方建议使用Gradle Wrapper的原因。实际上有了Gradle Wrapper之后,我们本地是可以不配置Gradle的,下载Gradle项目后,使用gradle项目自带的wrapper操作也是可以的。
在某种程度上,Grooovy可以被视为Java的一种脚本化改良版,Groovy也是运行在JVM上,它可以很好地与Java代码以及相关的库进行交互操作。它是一种成熟的面向对象编程语言,既可以面向对象编程,又可以用做纯粹的脚本语言。大多数有效的Java代码也可以转换为有效的Groovy代码,Groovy和Java语言的主要区别是:完成同样的任务所需的Groovy代码比Java代码更少。其特点为:
1、Groovy是基于Java语言的,所以完全兼容Java语法,可以作为面向对象编程语言【定义类】,也可以作为脚本型语言【文件定义中不出现类】。
2、在一个groovy文件中可以混合类的定义和脚本定义,此时不要定义一个和文件同名的类。
3、groovy中使用def定义变量、方法,不建议使用具体的数据类型。
4、groovy中的注释:单行注释用//,多行注释用:/**/。
5、groovy中语句末尾的分号是可以省略的,以换行符作为结束。
6、默认类、方法、字段都是public修饰的。
7、支持顺序结构、分支结构、循环结构语句。
8、支持各种运算符:算术、关系、位、赋值、范围运算符。
9、基本类型也是对象,可以直接调用对象的方法。
10、groovy中的字符串有单引号:作为字符串常量使用,没有运算能力;双引号:可以引用变量${},有运算能力;三个引号:模版字符串,支持换行。
11、数据类型:变量,属性,方法,闭包的参数以及方法的返回值的类型都是可有可无的,都是在给变量赋值的时候才决定它的类型。
类型转换:当需要时,类型之间会自动发生类型转换:字符串(String)、基本类型(如int)和类型的包装类(如Integer);
类说明:如果在一个groovy文件中没有任何类定义,它将被当做script来处理,也就意味着这个文件被透明的转换为一个Script类型的类,这个自动转换的类将使用原始的groovy文件名作为类的名字。groovy文件的内容被打包进run方法,另外在新产生的类中被加入一个main方法以进行外部执行该脚本。
Gradle项目的生命周期分为三大阶段:Initialization->Configuration->Execution.每个阶段都有自己的职责,具体如下图所示:
**Initialization阶段:**主要是初始化构建,它又分为两个子过程,一个是执行Init Script,另一个是执行Setting Script。
**Configuration阶段:**这个阶段开始加载项目中所有模块的Build Script。所谓“加载”就是执行build.gradle中的语句,根据脚本代码创建对应的task,最终根据所有task生产由Task组成的有响无环图(Directed Acyclic Graphs),如下:
从而构成如下有向无环树:
Execution阶段:这个阶段会根据上个阶段构建好的有向无环图,按着顺序执行Task【Action动作】。
首先对settings文件做以下几点说明:
1、作用:主要是在项目初始化阶段 确定一下引入哪些工程需要加入到项目构件中,为构建项目工程树做准备。
2、工程树:gradle中有工程树的概念,类似与maven中的project与module。
3、内容:里面主要定义了当前gradle项目及子project的项目名称。
4、位置:必须放在根工程目录下。
5、名字:为settings.gradle文件,不能发生变化。
6、对应实例:与org.gradle.api.initialization.Settings实例是一一对应的关系。每个项目只有一个settings文件。
7、关注:作为开发者我们只需要关注该文件中的includeing方法即可。使用相对路径【:】引入子工程。
8、一个子工程只有在setting文件中配置了才会被gradle识别,这样在构建的时候才会被把包含进去。案例如下所示:
项目名称中":“代表项目的分隔符,类似路径中的”/“,如果”:"开头则表示相对于rootProject。然后Gradle会为每个带有build.gradle脚本文件的工程构建一个与之对应的Project对象。
项目实质上是Task对象的集合。一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令。另外,一个Task可以读取和设置Project的Property以完成特定的操作。
在文件所在的目录执行命令:gradle A。
提示1:task的配置是在配置阶段完成的。
提示2:task的doFirst、doLast方法是执行阶段完成的,并且doFirst在doLast执行之前执行。
提示3:区分任务的配置阶段和任务的行为,任务的配置是在配置阶段执行,任务的行为是在任务执行阶段执行。
案例如下:doFirst、doLast两个方法可以在任务内部定义,也可以在任务外部定义
测试:gradle a,输出如下所示:
Task只见的依赖关系可以在以下几个部分设置:
方式一:参数方式依赖
方式二:内部依赖
方式三:外部依赖
当然:task也支持跨项目依赖。
拓展1:当一个Task依赖多个Task的时候,被依赖的Task之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响。
拓展2:重复依赖的任务只会执行一次,比如:A——>B、C;B——>C;任务A依赖任务B和任务C、任务B依赖C任务。执行任务A的时候,显然任务C被重复依赖了,C只会执行一次。
#常见的任务
#构建项目,编译、测试、打包等操作
gradle build
#运行一个服务,需要application插件支持,并且指定了主启动类才能运行
gradle run
#清理当前项目的build目录
gradle clean
#初始化gradle项目
gradle init
#生成wrapper文件件的
gradle wrapper
#项目报告相关任务
#列出所选项目及子项目列表,以层次结构的形式显示
gradle projects
#列出所选项目【当前projects,不包含父、子】的已分配给任务组的哪些任务
gradle tasks
#列出所选项目的所有任务
gradle tasks -all
#列出所选项目中指定分组中的任务
gradle tasks -group="build setup"
#显示某个任务的详细信息
gradle help -task someTask
#查看整个项目的依赖信息,以依赖树的方式显示
gradle dependencies
#列出所选项目的属性列表
gradle properties
#调试相关选项
#查看帮助信息
-h,-help
#打印Gradle、Groovy、Ant、JVM和操作系统版本信息
-v,-version
#打印出所有异常的完整堆栈跟踪信息
-S,-full,-stacktrace
#打印出用户异常的堆栈跟踪信息
-s,-stacktrace
#调试Gradle守护进程
-Dorg.gradle.daemon.debug=true;
#指定启用调试时要侦听的端口号,默认值为5005
-Dorg.gradle.debug.port=(port number)
#性能选项,一般在gradle.properties中指定这些选项中的许多选项,因此不需要命令行标志
#尝试重用先前版本的输出,默认关闭(off)
--build-cache,--no-build-cache;
#设置Gradle可以使用的worker数。默认值是处理器数
--max-workers
#并行执行项目,默认设置为关闭(off)
-parallel,-no-parallel
#守护进程选项
#使用Gradle守护进程运行构建,默认是on
-daemon,-no-daemon
#在前台进程中启动Gradle守护进程
-foreground
-Dorg.gradle.daemon.idletimeout=(number of milliseconds)
#将在这个空闲时间的毫秒数之后停止自己。默认值为10800000(3小时)
Gradle Daemon
#日志选项
-Drog.gradle.logging.level=(quiet,warn,lifecycle,info,debug);
#通过Gradle属性设置日志记录级别
#只能记录错误信息
-q,-quiet
#设置日志级别为warn
-w,-warn
#将日志级别设置为info
-i,-info
#登录调试模式(包括正常的堆栈跟踪)
-d,-debug
#其它
#等价于:-exclude-task,常见gradle -x test clean build
-X:-x
#强制执行任务,忽略up-to date,常见gradle build -return-tasks
-return-tasks
#忽略前面失败的任务,继续执行,而不是在遇到第一个失败时立即停止执行,每个遇到的故障都将在构建结束时报告,常见:gradle build continue
-contiune
#将maven项目转换为gradle项目(根目录执行)
gradle init -type pom
#执行自定义任务
gradle[`taskName]
扩展1:gradle任务名时缩写:任务名称支持驼峰式命名的任务名缩写,如:connectTask简写为:cT,任务执行任务gradle cT。
拓展2:gradle默认各指令之间相互的依赖关系:
任务定义方式,总体分为两大类:一种是通过Project中的task()方法,另一种是通过tasks对象的creat或者register方法。
task('A',{// 任务名称,闭包都作为参数
println "taskA..."
})
task('B){// 闭包作为最后一个参数可以直接从括号中拿出来
println "taskB..."
}
task C{// groovy语法支持省略方法括号
println "taskC..."
}
def map=new HashMap();
map.put("action",{println "taskD"}) //action属性可以设置为闭包
task(map,"D");
task.create(' E'){// 使用tasks的creat方法
println "taskE..."
}
tasks.register('f'){
println "taskF..."
}
常见的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 | 组装JAR存档文件 |
Test | 执行Junit(3.8x、4.x或5.x)或TestNG测试 |
Upload | 将Configuration的构件上传到一组存储库 |
War | 组装WAR档案 |
Zip | 组装ZIP归档文件。默认是压缩ZIP的内容 |
在Gradle中有三种方式可以指定Task执行顺序:
1、dependsOn强依赖方式
2、通过Task输入输出
3、通过API指定执行顺序
gradle的强大功能不仅仅用于定义任务的功能。列如,可以使用它在循环中注册同一类型的多个任务:
4.times{counter->
tasks.register("task$counter"){
doLast{
println "I am task bumer $counter"
}
}
}
一旦注册了任务,就可以通过API访问它们。列如,您可以使用它在运行时动态地向任务添加依赖项。Ant不允许这样的事情发生。
4.times{counter->
tasks.register("task$counter"){
doLast{
println "I am task bumer $counter"
}
}
}
tasks.named('task0'){dependsOn('task2','task3')}
构建4个任务,但是任务0必须依赖于任务2和任务3,那么代表任务2和任务3需要在任务0之前优先加载。具体测试如下:
每个任务都有一个enabled默认标志true。将其设置为false阻止执行任何任务动作。禁用的任务将标记为“跳过”。
task disableMe{
doLast{
println 'This task is Executing...'
}
enable(true)// 直接设置任务开启,默认值为true
}
// disableMe.enable=false 设置关闭任务
每个任务都有一个timeout可用于限制其执行时间的属性。当任务达到超时,其任务执行线程被中断,该任务将被被标记为失败,终结器任务任将运行。如果-continue使用,其任务可以在此之后继续运行。不影响中断的任务无法超时。Gradle的所有内置任务均会及时响应超时。
task a(){
doLast{
Thread.sleep(1000)
println "当前任务a执行了"
}
timeout=Duration.ofMillis(500)
}
task b(){
doLast{
println"当前任务b执行了"
}
}
在控制台使用:gradle a b测试会发现执行a的时候,由于a执行超时,抛出异常,所以没有继续往下执行【b也没执行】。
然后在控制台使用:gradle a b -continue,测试会发现a虽然失败,但是b还是执行了。
常用的任务查找方法有:
task test{
doLast{
println "精诚所至金石为开!"
}
}
// 根据任务名查找
tasks.findByName("test").doFirst({println "遇事不决便问春风"})
tasks.getByName("test").doFirst({println "春风不语"})
// 根据任务路径查找【相对路径】
tasks.findByPath("test").doFirst({println "春风、阳光、空气"})
tasks.getByPath("test").doFirst({println "都是幸福哦"})
当我们执行、依赖一个不存在的任务时,Gradle会执行失败,报错误信息。那我们能否对其进行改进,当执行一个不存在的任务时,不是报错而是打印提示信息呢?
task hello{
doLast{
println 'hello everyone'
}
}
tasks.addRule('对该规则的一个描述,便于调试、查看等'){
String taskName->task(taskName){
doLast{
println '该${taskName}任务不存在,请查证后再执行'
}
}
}
测试:使用gradle abc hello
进行测试,此时当abc任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行hello任务。此外,它还可以根据不同的规则动态创建需要的任务等情况。
断言就是一个条件表达式。task有一个onlyIf方法。它接受一个闭包作为参数,如果该闭包返回true则该任务执行,否则跳过。这有很多用途,比如控制程序那些情况下打什么包。什么情况下执行单元测试,什么情况下执行单元测试的时候不执行网络测试等。具体案列如下所示:
task hello{
doLast{
println 'hello worild!'
}
}
hello.onlyIf{!project.hasProperty('fancy')}
测试:通过-P为Project添加fancy属性
gradle hello -Pfancy
Gradle允许您定义一个或多个在没有指定其他任务时执行的默认任务。
代码如下:
defaultTasks 'myClean','myRun'
tasks.register('myClean'){
doLast{
println 'Default Cleaning!'
}
}
tasks.register('myRun'){
doLast{
println 'Default Running!'
}
}
tasks.register('other'){
doLast{
println 'I am not a default task!'
}
}
几种常见的文件操作方式:
使用Project.file(java.lang.Object)方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前project[根project或者自project]的目录。其实使用Project.file(java.lang.Object)方法创建的File对象就是Java中的File对象,我们可以使用它就像在Java中一样。示例代码如下:
// 使用相对路径
File configFile=file('src/config.xml')
configFile.createNewFile();
// 使用绝对路径
configFile=file('D:\\conf.xml')
println(configFile.createNewFile())
// 使用一个文件对象
configFile=new File('src/config.xml')
println(configFile.exists())
文件集合就是一组文件的列表,在Gradle中,文件集合用FileCollection接口表示。我们可以使用Project.files(java.lang.Object[])方法来获得集合对象,如下代码,创建一个FileCollection实例:
对于文件集合,我们可以遍历它;也可以把它转换成Java类型;同时还能使用+来添加一个结合,或使用-来删除集合。
文件树是有层级结构的文件集合,一个文件树它可以代表一个目录或者ZIP压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合的所有功能。我们可以使用Project.FileTree(java.utilMap)
方法来创建文件树对象,还可以使用过滤条件来包含或排除相关文件。实例底代码如下:
我们可以使用Copy任务来拷贝文件,通过它可以过滤指定拷贝内容,还能对文件进行重命名操作等。Copy任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用CopySpec.form(java.lang.Object[])
方法指定源文件;使用CopySpec.into(java.lang.Object[])
方法指定目标目录。实例代码如下:
task copyTask(type:Copy){
from 'src/main/resources'
into 'build/config'
}
通常一个项目会有很多的Jar包,我们希望把项目打包成一个WAR,ZIP或TAR包进行发布,这时我们就可以使用Zip,Tar,Jar,War和Ear任务来实现,不过它们的用法都一样,所以在这里只介绍Zip任务的示例。
apply plugin:'java'
version=1.0
task myZip(type:Zip){
from 'src/main'
into 'build' //保存到build目录中
baseName='myGame'
}
println myZip.archiveName
Gradle中的依赖分别为直接依赖、项目依赖、本地jar依赖。
案例如下:
dependencies{
// 1、依赖当前项目下的某个模块[子工程]
implementation project(':subject01')
// 2、直接依赖本地的某个jar文件
implementation files('libs/foo.jar','libs/bar.jar')
// 3、配置某文件夹作为依赖项
implementation fileTree(dir:'lib',include:['*.jar'])
// 4、直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2''
}
当执行build命令时,gradle就会去配置的依赖仓库中下载对应的jar,并应用到项目中。
类似于Maven的scope标签,gradle也提供了依赖的类型,具体如下所示:
依赖冲突是指“在编译过程中,如果存在某个依赖的多个版本,构建系统应该选择哪个进行构建的问题”,如下所示:
A、B、C都是本地子项目module,log4j是远程依赖。
解决方案:
不允许传递依赖:
简单的说,通过插件我们可以:
1.促进代码重用、减少功能类似代码编写、提升工作效率
2.促进项目更高程度的模块化、自动化、便捷化
3.可插拔式的扩展项目的功能
在项目构件工程中做很多事情,把插件应用到项目中,通常可以完成:
脚本插件的本质是一个脚本文件,使用脚本插件时通过apply form:
将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在build.gradle文件中使用它,具体如下:
// version.gradle文件
ext{
company='久其'
cfgs=[
compileSdkVersion:JavaVersion.VERSION__1_8
]
spring=[
version:'5.0.0' ]
}
下面将在构件文件中使用这个脚本文件,具体如下:
//build.gradle文件
apply from:'version.gradle'
task taskVersion{
doLast{
println "公司名称为:${company},JDK版本是¥{cfgs.compileSdkVersion},版本号是${spring.version}"
}
}
意义:脚本文件模块化的基础,可以按功能把我们的脚本进行拆分为一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
二进制插件【对象插件】就是实现了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.plugin.JavaPlugin,所以可以使用如下方式使用Java插件:
//使用方式1:Map具名参数.全类名
apply plugin:org.gradle.api.plugins.JavaPlugin
//使用方式2:org.gradle.api.plugins默认导入
apply plugin:JavaPlugin
// 使用方式3:插件的id
apply plugin:'java'
第二种:对象插件之第三方插件
如果使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径
// 使用传统的应用方式
buildscript{
ext{
springBootVersion="2.3.3.RELEASE"
}
repositories{
mavenLocal()
maven{url 'http://maven.aliyun.com/nexus/content/groups/publics'}
}
// 此处先引入插件
dependencies{
classpath("org.springframework,boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
//再应用插件
apply plugin:'org.springframework.boot'// 社区插件,需要先事先引入,不必写版本号
但是如果第三方插件已经被托管在https://plugins.gradle.org/
网站上,就可以不用再buildscript里配置classpath依赖了,直接使用新出的plugins DSL的方式引用,案列如下:
plugins{
id'org.springframework.boot'version'2.4.1'
}
第二种:对象插件之用户自定义插件
Project和Task都允许用户添加额外的自定义属性,添加额外的属性,通过应用所属对象的ext属性即可实现。添加之后可以通过ext属性对自定义属性读取和设置,如果同时添加多个自定义属性,可以通过ext代码块:
// 自定义一个Project属性
ext.age=18
// 通过代码块同时自定义多个属性
ext{
phoe=123456789
address="美丽的草原"
}
task extCustomProperty{
//在task中自定义属性
ext{
desc="奥利给"
}
doLast{
println"年龄是:${age}"
println"电话是:${phone}"
println"地址是:${address}"
println"口号:${desc}"
}
}
buildscript里是gradle脚本执行所需要依赖,分别是对应的maven库和插件。
案例如下:
需要注意的是:
接下来,将我们写好的模块发布到公司的私服以供别人使用,如下图所示:
// 带源码和javadoc的发布:需要‘java-library’插件支持,它是java的升级版,java插件的功能java-library都有
```powershell
publishing{
publications{
myLibrary(MavenPublication){
groupId='org.gradle.sample' //指定GAV坐标信息
artifactId='library'
version='1.1'
from components.java //发布jar包
//from components.web //引入war插件,发布jar包
}
}
}
repositories {
// 本地仓库
mavenLocal()
// 发布到项目私服中
maven {
url "https://nvwa.x.com.cn/nexus/repository/maven-nvware-group/"
// 认证信息:用户名和密码
credentials {
username nexusUsername
password nexusPassword
}
allowInsecureProtocol = true
}
}