持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
前言
相信大家都听过Gradle了,他也是一个构建工具,同Maven一样,但他的速度比Maven更快,Gradle构建脚本是使用Groovy或Kotlin DSL编写的,或许这个原因也是大部分人不使用的原因吧,但是他确实比Maven更方便更快,举个例子,如果你要构建一个Maven项目,是不是需要手动安装Maven,而Gradle就不需要这样了,你从github上下载任何一个Gradle的项目后,在项目根目录执行./gradlew build就可以开始构建项目了,第一步其实也是下载Gradle构建工具,下载后位于/home/用户/.gradle/wrapper/dists/下,只不过这部分通过一个自带的脚本已经帮你完成了,Maven其实也可以这样做,但是他貌似没有,其他更加方便地方就不说了,一句话,使用Gradle准没错。
所以打算写一个系列,来介绍一下Gradle,下面是本系列目录。
- Task
- Project、Task常用API
- 文件操作
- 依赖管理
- 多模块构建
- 插件编写
- SpringBoot插件源码分析
- 过度到Kotlin
Groovy
本章是第一章,所以有必要了解下Groovy。
我们都知道,值钱的是JVM,当你具有编译原理和class文件方面的知识后,就可以写出自己的语言,并跑在JVM上,Groovy就是其中之一,它既可以用于面向对象编程,也可以用作纯粹的脚本语言。
让我们看一段Groovy的代码。
new File('/home/HouXinLin/test/a.txt').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}
这段代码的作用是读取并打印指定文件中所有行,当然,写法还有很多,比如下面这样。
Files.readAllLines(Paths.get("/home/HouXinLin/test/a.txt")).each {
println(it)
}
我们知道Java中必须从main方法开始执行,但作为脚本语言形式,第一行就可以开始执行,不需要创建类,但如果你需要的话,同样可以使用类。
我们说点和Java不同的,比如下面代码。
def range = 1..10;
range.each { println it}
def用来定义变量,..被称为范围运算符,上述这个范围就是1-10,他的返回值是一个List,可以用get等方法对这个变量操作,除了Java本身的方法,Groovy也扩展了众多,丰富了我们对Java对象的操作,一个很有特色的是Groovy还"扩展"了Object上的方法,但并不是真的在Object中增加了新方法,只是让我们用起来觉得这是在调用Object上的方法,比如下面这段代码
class User{
String name;
String addr;
}
def user =new User()
println(user.properties)
他的作用是获取User中的所有属性,输出如下。
[name:null, addr:null, class:class com.main.User]
在Java中,需要通过反射去获取,比较繁琐,而Groovy天然支持这种操作,当然对反射调用方法同样也是天然支持,极大简化了以往的代码量。
class User{
void print(name){
println (name)
}
}
def user =new User()
user.invokeMethod("print","name")
对于已经会java或者kotlin的人来说,Groovy学习成本低,使用Gradle也避不开使用他,但这里就不详细介绍了。
闭包
但是还是得说一下闭包,因为在使用Gradle时,很多时候都是以闭包形式出现,闭包是一个匿名的代码块,可以接受参数、返回值。闭包中也可以引用其他地方声明的变量。
如下,相当于声明了一个没有名字的函数,代码块位于{}中,并将引用赋值给hello变量,这样当调用时,使用call方法即可。
def hello={
println("hello")
}
hello.call()
也可以直接加()调用。
hello()
并且闭包都是Closure的实例。
def hello={
println("hello")
}
println( hello instanceof Closure)
如果需要参数,也非常方便
def hello={name->
println(name)
}
hello("张三")
闭包中的返回值默认是最后一行,如果有return 那么结果就是return的结果。
def hello={String name->
println(name)
name
}
println(hello("as"))
Gradle
同Maven一样,我们需要告诉Gradle一个"配置文件",Maven中的配置文件是pom.xml,Maven会根据里面引入的依赖或者插件去构建你的项目,而Gradle的配置文件是build.gradle,安装好Gradle工具后,可以通过gradle init命令来创建一个标准的Gradle项目,创建完成后通常结构设这样的。
ls
build.gradle gradlew gradlew.bat settings.gradle
-
build.gradle是我们的学习核心,大部分配置都需要在这个文件中更改。
-
gradlew注意这里有个w,w的意思是wrapper,意味包装,使用Gradle必须安装这是无疑的,但问题是需要手动还是自动呢?答案是自动,当gradlew脚本第一次在本地电脑上运行时,它自己会从网上下载Gradle,并缓存它,这就是开头我们说的,在任何地方都可以非常轻松地构建项目,这个文件还依靠gradle/wrapper/下的gradle-wrapper.jar、gradle-wrapper.properties这两个文件。如果安装好了Gradle,那么gradlew会把参数传递给它进行下一步构建。
-
gradle-wrapper.jar用于下载Gradle的jar 文件。
-
gradle-wrapper.properties 负责配置Gradle的属性文件,例如与此版本兼容的Gradle版本
-
-
gradlew.bat这是windows版本中的gradlew。
-
settings.gradle 主要在多模块的时候使用,用于配置所有包含的子模块.
还有一个 gradle.properties是可选的,它的主要目的是配置gradle本身的启动选项。
目录
Gradle有两个主要目录, 用户主目录和项目根目录,用户目录位于$USER_HOME/.gradle,用于存储全局配置属性和初始化脚本以及缓存和日志文件,这个目录结构比较多,后续下载的依赖也都在这个目录下,而由于每个项目可能会使用不同Gradle版本,那么不同的版本也都位于这里。
项目根目录就是配置我们项目的,主要信息就是由build.gradle文件确定。
任务
下面我们编写第一个Gradle构建代码。
#build.gradle
task hello {
println 'hello gradle'
}
使用./gradlew hello运行这个任务,会看到输出。
./gradlew hello
> Configure project :
hello gradle
这里提到了任务,没错,任务是一个很好理解的词,Gradle构建由不同的任务组成,上面我们用task定义一个名为hello的任务,这个任务的任务就是输出一段话,如果用它来构建SpringBoot项目,打包时,我们会执行./gradlew bootJar,bootJar这个任务是Spring团队编写,负责将SpringBoot项目打包成一个jar文件。
还有个词叫项目(Project),每个build.gradle就对应一个Project,Gradle在执行build.gradle时都会创建一个Project对象,我们可以用this获取这个Project对象,所以也可以说,build.gradle是对一个Project的配置,在后续我们会构建一个多项目的环境,也可以叫多模块,在这个环境中,我们会看到很多build.gradle,每个build.gradle会用来配置对应模块。
Task和Project都有众多的API可供我们调用,使用Project中Api时,this可以省略,如果要查看有哪些API可以调用,可以一到 docs.gradle.org/current/jav… 这里查看。
每个Task都属于一个Project,可以使用各种方法来创建和查找任务实例。例如,TaskContainer.create(java.lang.String)使用指定名称创建一个空任务,这里的TaskContainer对象可以从Project中提供的getTasks方法获取,也可以直接使用tasks。
Task创建的时候还可以通过下面这种方法指定一个Type,Type告诉Gradle我这个Task对象会从哪个基类Task派生。Gradle本身提供了一些通用的Task,最常见的有Copy任务,当我们通过task hello(type:Copy) 创建的时候,创建的Task就是一个Copy Task,用来做文件复制,这个操作我们在下一篇文章会说。
task myTask(type: SomeType) // SomeType可以指定任务类型,Gradle本身提供有Copy、Delete、Sync等
下面演示下通过this获取当前项目名、遍历所有项目。
task taskA {
doFirst {
println ('当前项目名称:'+this.getName())
this.getAllprojects().forEach{
println(it.name +" "+it.projectDir)
}
}
}
在任务中,就可以使用Groovy的语法,比如下面代码,其作用是打印1..10
task hello {
def list =1..10
list.each {
println(it)
}
}
doLast、doFirst
在上面你会发现我们的代码在doFirst块中运行,也可以直接在Task块中运行,其实还有doLast 。
task hello {
doLast {
println("doLast")
}
doFirst {
println("doFirsh")
}
println("hello")
}
doFirst和doLast是两个不同任务块,在doFirst块中,用于编写首先执行的任务,而doLast块用于最后执行的任务,输出如下。(这里就是闭包代码块)
> Configure project :
hello
> Task :hello
doFirst
doLast
你会发现,先打印hello,然后才会依次执行doFirst、doLast,这是因为Gradle有三个不同的阶段,也被称为生命周期。
-
初始化
Gradle在初始化阶段会确定哪些项目将参与构建,并为每个项目创建一个Project实例。
-
配置
解析所有的Project对象中的Task,构建好所有Task的拓扑图,在上面hello被打印时,正处于这个阶段。
-
执行
执行具体的Task及其依赖Task,这个阶段执行的就是doFirst、doLast。
register
还可以使用下面方式声明一个任务
tasks.register ("hello") {
println("hello")
}
灵活注册
下面代码通过一个循环,注册4个任务,任务名分别是task1-task4,并在hello任务中依赖task1,所以当执行hello时,会先将task1执行完毕。
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "task-$counter"
}
}
}
task hello(){
dependsOn "task1"
doLast {
println ("hello")
}
}
结果如下
> Task :task1
task-1
> Task :hello
hello
任务依赖
dependsOn用于将给定的依赖项添加到此任务,如下,在执行test任务时,需要先将hello任务执行完毕。
tasks.register('hello') {
doLast {
println 'hello gradle!'
}
}
tasks.register('test') {
dependsOn "hello"
doLast {
println "test task"
}
}
还可以这样写,由方法参数指定依赖。
tasks.register('hello') {
doLast {
println 'hello gradle!'
}
}
task test(dependsOn:"hello"){
doLast {
println "test task"
}
}
操作现有任务
一旦注册了任务,就可以通过API访问它们,如下,tasks是TaskContainer类型,用于管理任务实例,其中named方法用来按名称查找任务,这段代码用来在原来hello任务后面继续增加一个doLast。
tasks.register('hello') {
doLast {
println 'hello gradle!'
}
}
tasks.named("hello"){
doLast{
println(it.getName())
}
}
也可以通过下面这种方法获取对应的Task实例。
def helloTask =tasks.named("hello").get()
然后你需要参考Task类,看看有什么方法可以调用,比如下面通过finalizedBy方法为hello任务增加一个最终执行的任务,这样当hello任务执行后,会自动执行finalized任务。
tasks.register('finalized') {
doLast {
println 'by!'
}
}
def helloTask =tasks.named("hello").get()
helloTask.finalizedBy("finalized")
使用方法
Gradle提供了大量API,这需要自己参考他们的文档,比如下面,使用file方法创建一个File对象,然后遍历其目录输出其下所有文件名。
tasks.register('hello') {
doLast {
file("/home/HouXinLin/project/gradle-test/").list().each{
println(it)
}
}
}
默认任务
如果在./gradlew后面不加任务名,那么不会执行任何任务,如果需要,可以指定一个默认的任务。
defaultTasks 'hello'
tasks.register('hello') {
doLast {
file("/home/HouXinLin/project/gradle-test/").list().each{
println(it)
}
}
}
任务跳过
如果在任务执行时,需要动态检测这个任务到底要不要执行,可以这样做。
tasks.register('hello') {
doLast {
file("/home/HouXinLin/project/gradle-test/").list().each{
println(it)
}
}
}
hello.configure {
onlyIf { project.file("/home/HouXinLin/test/a.txt").exists() }
}
使用onlyIf()方法并返回一个true或false,如果任务需要执行,则应该返回true,如果应该跳过任务,则返回 false。在这里,我们检测指定文件是否存在,如果不存在,则跳过。
为任务添加描述
当要为任务添加一个描述时,需要这样做。
tasks.register('hello') {
description '遍历目录'
doLast {
file("/home/HouXinLin/project/gradle-test/").list().each{
println(it)
}
}
}
这样当执行./gradlew help --task hello后,将会显示这个描述。
finalizedBy
通常在java中,会使用finally来做一些资源清理,同样在gradle中也可以,通过finalizedBy指定一个任务名,当本任务执行完成后,会调用目标任务。
tasks.register('by') {
doLast {
println 'by'
}
}
task test(){
doLast {
println "test task"
}
finalizedBy("by")
}
自定义任务
task xx这种声明的任务仅在该项目中可用,如果要在不同项目中使用,复制过去这种做法已经习以为常,但是更好的做法是使用自定义任务,打包成一个jar后,所有项目依赖这个jar即可。
自定义任务通过继承DefaultTask来创建,并使用@TaskAction注解来告诉Gradle这个方法将作为任务执行的方法。
在这里,为了简单起见,自定义任务在同一个build.gradle中定义。
abstract class HelloTask extends DefaultTask {
@Input
String message;
@TaskAction
def hello() {
println "hello ${message}"
}
}
task hello(type:HelloTask){
message="gradle"
}
这段代码会输出hello gradle,注意的是,参数输入必须加入@Input注解。
同样也可以通过构造方法接收参数,同时构造方法中也必须加入@javax.inject.Inject注解。
abstract class HelloTask extends DefaultTask {
@Input
String message;
@Inject
HelloTask(String message) {
this.message = message
}
@TaskAction
def hello() {
println "hello ${message}"
}
}
tasks.register("hello",HelloTask,"gradle")