Gradle基础---构建+插件编写+插件分析(笔记二)

上一篇文章对gradle基础概念进行了笔记https://blog.csdn.net/qq_20395245/article/details/105454881,这篇文件将对构建,插件编写和插件分析的基础进行记录。首先,还是使用笔记一中手动按照gradle wrapper官网说明声明构建的gradle-demo项目为例,由于gradle项目是可以被IDeaJ识别的,所以这里选用用IDeaJ直接Open打开,打开后可以看到在IDE中会先进行构建过程:

Gradle基础---构建+插件编写+插件分析(笔记二)_第1张图片

Gradle基础---构建+插件编写+插件分析(笔记二)_第2张图片

耐心等待,最后左下角gradle-demo:load build...变成gradle-demo:success就完成导入构建了,同时会发现gradle还执行了一个任务,输出了build.gradle中的println "helloworld"任务内容,这就引出了gradle lifecycle,也就是gradle生命周期。

Gradle生命周期:官方文档https://docs.gradle.org/current/userguide/build_lifecycle.html。

Gradle基础---构建+插件编写+插件分析(笔记二)_第3张图片

从官方文档中,我们可以了解到gradle生命周期有三个步骤:初始化-->配置-->执行。每个步骤会进行各自的工作,下面对每个步骤进行描述,这个生命周期有助于了解一个gradle文件中各个任务的执行顺序和执行结果。

(1)Initialization初始化阶段:

根据官方描述,gradle支持单项目及多项目构建,在初始化这个阶段,gradle会识别哪些项目会参与到构建当中,同时会为这些参与构建的项目分别创建各自的Project实例(Project是Gradle内部重要的一个属性,后面还会继续说明)。但是gradle怎么识别哪些项目要参与构建呢,文档中也做了说明,就是一个默认的settings.gradle文件,这个文件在初始化期间就执行完成了,如我们的gradle-demo中新建一个settings.gradle加上一段输出,在build.gradle中也加上一段输出,重新构建这个项目可以看到:

Gradle基础---构建+插件编写+插件分析(笔记二)_第4张图片

会先输出settings.gradle里的内容,然后再进行build.gradle的构建,这也就能解释gradle在构建前可以识别哪些项目需要参与到真正构建的原因了。特别说明的是对于多项目构建必须要有这个settings file,当引入其他项目时需要使用include声明并引入到 build script classpath中,例如settings.gradle定义,其中projectXXX这些项目是需要进行编译才能被使用,可以提前编译或在gradle中进行compile编译:

include 'project1', 'project2:child', 'project3:child1'
project(':projectA')

对于子项目的调用可以使用project.subproject.XXXX方式。另外对于单项目构建这个setting file是可选的。

(2)Configuration配置阶段:当所有的project实例都初始化完成后,就开始对这些project实例中参与构建的任务,如task,compileJava,plugins等进行配置,本质上可以理解为将build.gradle文件中定义的配置从上到下先执行一遍,会对build,gradle外部的配置,以及如task属性等内部的非子方法进行扫描配置(以task为例可以理解为task内具体某个子方法配置的闭包内容不会被扫描),这个过程可参考官网样例,gralde-demo中参考样例进行修改后执行如下:

Gradle基础---构建+插件编写+插件分析(笔记二)_第5张图片

对于build.gradle外部定义以及task任务内部非子方法的内容进行了扫描配置,那么有个问题了,像taskB任务中的子方法doFirst又是怎么参与构建的呢,实际上当配置阶段扫描到这些子方法时,会识别gradle API的方法如doFirst和doLast,这些方法本身带有顺序会根据顺序被配置到这taskB任务的调用链上,其中doFirst是在调用taskB任务第一个触发的方法,doLast会在taskB执行结束后触发,执行的东西就是doFirst或doLast的闭包内容,这些是在具体执行阶段进行,所以继续引出最后一个Execution执行阶段。

(3)Execution执行阶段:这个阶段所有的project和内部配置都已经扫描配置好了,就可以进到这个构建项目中,通过具体的构建命令对这些构建配置进行各种调用,例如gradlew B

Gradle基础---构建+插件编写+插件分析(笔记二)_第6张图片

这里就可以看出配置阶段扫描进行执行阶段,在执行taskB这个任务时,分别前后触发了内部定义的子方法。

以上就是gradle的生命周期,相信了解了这些会有助于更好看懂gradle文件的构建过程,接下来看下gradle的核心。

Gradle核心(API):gradle的核心是project,这就是每个参与构建的项目在jvm中的项目实例,gradle的源码api可以在ideaj工具

查看,ctrl点击build.gradle任意一个Task,就可以打开Project的源码API,实际上是一个接口,内部定义了很多项目实例的属性,包含我们常用的Version,Task,BuildDir等属性,也提供了一个getXXX用于获取相关的属性或实例,这些API为gradle文件的脚本编写提供了底层实现,感兴趣可以自行做深入研究了解,下面查看另一个重要的核心Task(Project的一个属性)。

Gradle基础---构建+插件编写+插件分析(笔记二)_第7张图片

Task:这个属性有个重要的特性就是对于gradle而言,task是活的是动态的,通俗讲是可以动态通过名称来调用不同的task,而不需要将所有的task都声明出来而是通过动态创建,例如:

Gradle基础---构建+插件编写+插件分析(笔记二)_第8张图片

执行其中的task2:gradlew task2

对于Task内部也有很多重要的属性,例如DependsOn,这个属性可以将多个task任务进行关联依赖,

Gradle基础---构建+插件编写+插件分析(笔记二)_第9张图片

Gradle基础---构建+插件编写+插件分析(笔记二)_第10张图片

由执行结果可以知道,当存在依赖任务时,被依赖的任务其生命周期会被先执行,这个与java中父子集成的原则是一致的。

此外对于Project还有一个特殊的特性就是钩子函数,比如afterEvaluate钩子函数。这是一个特殊的函数在配置过程中被识别后会

先不执行,而是先放入到任务列表中,当gradle将这gradle文件从上到下全部配置完成会自动触发afterEvaluate钩子,进而执行这个钩子的内部闭包内容,可以用下面例子来展示这个过程:

Gradle基础---构建+插件编写+插件分析(笔记二)_第11张图片Gradle基础---构建+插件编写+插件分析(笔记二)_第12张图片

插件Plugins的编写:在项目中,有些任务逻辑等是共同的可能会被大部分地方引用,这个时候如果在这些地方都复制粘贴过去,

会显得特别繁琐和冗余,也不方便维护。在java中可以考虑使用封装复用,那么在gradle中则可以封装成一个个的插件plugins,Plugins在Project示例中,从API可知Project接口继承了PluginAware接口从而可以通过Apply方法引入Plugin,由于gradle是java代码编写的工具,所以在gradle文件中实际上是可以直接使用java语言进行编写的,对于插件的编写也同样使用java语言进行自定义,下面示例写一个简单的插件:

//在gradle文件中自定义一个plugin插件:采用java方式编写
class myPlugin implements Plugin{
    @Override
    void apply(Project target) {
        println "this is my plugin,I am created and be applied..."
        (0..<5).each{ i ->
            target.task('task' + i){ //注意这些task是属于传递进来的project的
                def count = i;
                doLast{
                    println "Executing task ${count}..."
                }
            }
        }
    }
}

//通过apply调用插件

apply plugin: myPlugin

Gradle基础---构建+插件编写+插件分析(笔记二)_第13张图片

这里就可以看到自定义的plugin生效了,从输出可以看到,plugin的apply也是在配置阶段会进行扫描的,在插件内的内容同样遵循上面描述的生命周期执行规则,特别需要注意的是如果说这个自定义的java编写的插件写在其它地方在build.gradle引入时,为了让配置期能识别到这个引入的自定义插件,则需要将该插件在buildscript的dependencies的classpath中声明出来使得配置期可以找到这个插件,为什么需要这样做在下面会有原因。

apply方法从API可以看到可以传入很多类型的plugin数据结构对象,甚至可以apply一个远程网络地址的script plugin,这里就不再进行延伸,如果有接触过相关gradle项目,经常会看到:

apply plugin{ id 'java'}或apply plugin: 'java'等写法;这本质上就是调用了一个名为java的插件,java插件中的apply方法会被执行一遍。对于插件,这里也还有两个重要的插件概念,就是buildSrc插件和Binary插件(即发布插件)。

理解buildSrc插件和Binary插件,首先需要理解配置阶段的classpath和编译java时的classpath,前者是配置阶段的classpath后者是执行阶段(如compileJava时)的classpath,下面通过一个例子来展示这个区别:

首先在gradle-demo中新建src.main.java.Main的java文件,引入main方法使用StringUtils工具类,但是这工具类在另一个jar包上所以需要在gradle中引入这个jar,图二构建生成org.apache.commons包后Main中就可以引入这个包的StringUtils工具类了:

Gradle基础---构建+插件编写+插件分析(笔记二)_第14张图片

Gradle基础---构建+插件编写+插件分析(笔记二)_第15张图片

这个过程实际上是运行阶段属于java的classpath,这个并不作用于配置期,也就是说这两个classpath是没有关联的:

Gradle基础---构建+插件编写+插件分析(笔记二)_第16张图片

那既然配置期的classpath和执行期编译java的classpath并不关联,那要怎么在配置期可以使用专属配置期的classpath呢?实际上可以使用buildscript,在内部独立配置仓库原,引用jar路径等,这里同样引入org.apache.commons就可以在配置期使用StringUtils工具类了,同时也会生成一个buildSrc目录。

Gradle基础---构建+插件编写+插件分析(笔记二)_第17张图片

看到这里如果有接触过gradle项目对于buildscript是不是就似曾相识了,原来是配置期的classpath。这里也回到上面提到的用java类编写了一个自定义个插件,如果要在build.gradle进行apply,那么就要在buildscript中声明到classpath中,自然也可以理解了。

BuildSrc插件:上面生成的buildSrc目录中,实际上是可以直接存放插件的,例如上面提到的自定义的插件也可以直接写在buildSrc\src\main\java目录中,这样就可以不在buildscript的classpath再声明就可以直接apply了,gradle会自动扫描这个包目录下所有的插件,这些插件就是BuildSrc插件。

Binary插件:顾名思义就是二进制插件,使用这个插件可以得到一些二进制文件比如class文件,然后在build.gradle中使用这个class对应的插件实例然后调用其apply方法,详细可以自行搜索相关的例子这里就不展开了。

插件分析:以spring5.1源码中的spring-context包下的build.gradle进行分析,文件可以去github下载spring5.1源码并且使用Idea构建出完整的spring源码结构(可参考https://blog.csdn.net/qq_20395245/article/details/105348043),对于学习gradle文件分析先从settings.gradle-->build.gradle-->spring-context.gradle的顺序先看。

Gradle基础---构建+插件编写+插件分析(笔记二)_第18张图片

由settings.gradle可知spring引入了很多的model表明这些子项目是要构建到源码项目中的,其中就包含了spring-context的子项目,看下build.gradle:

Gradle基础---构建+插件编写+插件分析(笔记二)_第19张图片

Gradle基础---构建+插件编写+插件分析(笔记二)_第20张图片

Gradle基础---构建+插件编写+插件分析(笔记二)_第21张图片

build.gradle主要声明了buildscript表明配置期需要的classpath环境,定义了多个configure插件,分别在插件内定义了很多运行期需要做的任务和classpath,以及ext中定义所需要的的数据集合(对于ext属性的官方文档描述,ext属性是ExtensionAware类型的特殊属性,本质是一个Map类型的变量),最后是一个def获取版本号的公共方法用于上面各个需要的地方直接复用调用。

Gradle基础---构建+插件编写+插件分析(笔记二)_第22张图片

看到spring-context.gradle文件,里面主要是将编译后的所需子工程进行依赖引入,其中optional是可选引入,并且以testCompile的方式在测试代码范围内引入依赖,testRuntime则在测试代码范围编译时不引入但在运行时引入依赖,为什么要作用在测试代码上呢?因为spring源码的编译可以通过编译test测试包编译。

关于依赖范围的问题引用一名网友的总结,在gradle里,对依赖的定义有6种,他们分别是compile, runtime, testCompile, testRuntime, providedCompile和providedRuntime,分别有各自不同的作用范围。

1)compile: 它表示程序需要引用这个库才能编译,如java中引入的spring framework。这些库在程序里有直接或者间接的引用。所以被定义为compile类型,大多数我们引用的库就是这个范围的。

2)runtime:  这也是java plugin引入的一个配置项。它默认的包含compile依赖。它主要表示这些依赖的库只是在运行的时候需要用到,但是在编译的时候并不需要。典型的比如jdbc的驱动程序。我们在应用程序里只是基于标准的jdbc api来开发,但是在程序具体运行的时候需要指定针对某个数据库的具体驱动程序。比如'mysql:mysql-connector:5.1.37'。

3)testCompile: 这也是java plugin引入的配置项。它默认会包含有compile的配置范围。它主要表示在测试代码里所涵盖的引用依赖。在程序代码里并不依赖它。一些常用的库比如JUnit, TestNG, Mockito等都适用于这个范围。

4)testRuntime: 这也是java plugin引入的配置。它默认包含有testCompile, runtime的依赖范围。主要表示测试的代码在运行的时候需要的依赖。这些依赖一般不直接用于测试代码的编译中。

5)providedCompile: 这是war plugin引入的配置。对于war plugin所涵盖的范围,它主要是引用在一些java web的程序中。所以有一些依赖的api,比如servlet api, 一些应用程序服务器提供的api在编译的时候不需要打包到war包里头,但是它们又需要引用它们,因为它们已经在所运行的服务器里包含了。

6)providedRuntime: 这也是war plugin引入的配置。它表示一些在编译的时候不需要提供的依赖库,但是在运行的时候需要。它们一般也是已经包含到应用服务器中了。

好了,以上就是所有关于gradle必备基础知识的分享,这两篇文章的内容点相信会对今后看gradle项目以及写构建脚本会有所帮助的。

你可能感兴趣的:(gradle_maven专栏)