• 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文件

    allprojects {
        repositories {
            mavenLocal() 
            maven { name "Alibaba"; url "https://maven.aliyun.com/repository/public" } 
            maven { name "Bstek";url "https://nexus.bsdn.org/content/groups/public/" } 
            mavenCentral()
        }
        buildscript {
            repositories {
                maven { name "Alibaba"; url 'https://maven.aliyun.com/repository/public' } 
                maven { name "Bstek"; url 'https://nexus.bsdn.org/content/groups/public/' } 
                maven { name "M2"; url 'https://plugins.gradle.org/m2/' }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启用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 版本

    # 下载的Gradle压缩包解压后存储的主目录
    distributionBase=GRADLE_USER_HOME
    # 相对于distributionBase的解压后的Gradle
    distributionPath=wrapper/dists
    # Gradle发行版压缩包的下载地址
    distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
    # 压缩包的路径同distributionBase,只不过是存放zip压缩包的
    zipStoreBase=GRADLE_USER_HOME
    # 同distributionPath,只不过是存放zip压缩包的
    zipStorePath=wrapper/dists
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:前面提到的 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 安装[非必须]和项目创建

    下载地址: https://groovy.apache.org/download.html

    安装完后配置好环境变量,创建项目时选择Groovy项目,同时指定对应的Groovy SDK

    3、Groovy基本类型

    • 类型转换:当需要时,类型之间会自动发生类型转换: 字符串(String)、基本类型(如int) 和类型的包装类 (如Integer)

    • 类说明:如果在一个groovy 文件中没有任何类定义,它将被当做 script 来处理,也就意味着这个文件将被透明的转换为一个 Script 类型的类,这个自动转换得到的类将使用原始的 groovy 文件名作为类的名字。groovy 文件的内容被打包进run 方法,另外在新产生的类中被加入一个main 方法以进行外部执行该脚本。

    在这里插入图片描述

    3.1 基本注意点

    方法调用时,在不含有歧义的地方可以省略方法调用时的括号。这类似于使用${变量名}时,括号在不引起歧义的地方可以省略是一样的

    def num1=1; 
    def num2= 2;
    println "$num1 + $num2 = ${num1+num2}"
    
    • 1
    • 2
    • 3

    3.2 引号说明

    def num1=1; 
    def num2=2;
    def str1="1d"; //双引号
    def str2='dsd'; //单引号
    //双引号运算能力,单引号用于常量字符串,三引号相当于模板字符串,可以支持换行
    println "$num1 + $num2 = ${num1 + num2}"
    //基本数据类型也可以作为对象使用,可以调用对象的方法
    println(num1.getClass().toString()) 
    println(str1.getClass().toString()) 
    println(str2.getClass().toString())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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 类导入

    参考官网地址:http://www.groovy-lang.org/structure.html#_imports

    Groovy 遵循 Java 允许 import 语句解析类引用的概念

    import groovy.xml.MarkupBuilder 
    def xml = new MarkupBuilder() 
    assert xml != null
    
    • 1
    • 2
    • 3

    Groovy 语言默认提供的导入

    import java.lang.* 
    import java.util.* 
    import java.io.* 
    import java.net.* 
    import groovy.lang.* 
    import groovy.util.*
    import java.math.BigInteger
    import java.math.BigDecimal
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.7 异常处理

    参考官网地址: http://www.groovy-lang.org/semantics.html#_try_catch_finally

    def z 
    try {
        def i = 7, j = 0 
        try {
            def k = i / j 
            assert false
        } finally {
            z = 'reached here'
        }
    } catch ( e ) {
        assert e in ArithmeticException 
        assert z == 'reached here'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.8 闭包

    参考:http://www.groovy-lang.org/closures.html

    闭包:Groovy 中的闭包是一个开放的、匿名的代码块,它可以接受参数、也可以有返回值。闭包可以引用其周围作用域中声明的变量。

    语法{ [closureParameters -> ] statements }
    其中[ closureParameters-> ]是一个可选的逗号分隔的参数列表,参数后面是 Groovy 语句。参数类似于方法参数列表, 这些参数可以是类型化的,也可以是非类型化的。当指定参数列表时,需要使用-> 字符,用于将参数与闭包体分离。

    //闭包体完成变量自增操作
    { item++ }
    //闭包使用 空参数列表 明确规定这是无参的
    { -> item++ }
    //闭包中有一个默认的参数[it],写不写无所谓
    { println it }
    { it -> println it }
    //如果不想使用默认的闭包参数it,那需要显示自定义参数的名称
    { name -> println name }
    //闭包也可以接受多个参数
    { String x, int y ->
        println "hey ${x} the value is ${y}"
    }
    //闭包参数也可是一个对象
    { reader ->
        def line = reader.readLine() 
        line.trim()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    闭包调用方式: 闭包是 groovy.lang.Closure 的实例。它可以像任何其他变量一样分配给一个变量或字段。 闭包对象(参数)闭包对象.call(参数)

    def isOdd = { int i -> i%2 != 0 } 
    assert isOdd(3) == true
    assert isOdd.call(2) == false
    
    def isEven = { it%2 == 0 } 
    assert isEven(3) == false 
    assert isEven.call(2) == true
    
    // ===============================
    
    //无参闭包
    def run(Closure closure){ 
        println("run start...")
        closure() println("run end...")
    }
    
    run {
        println "running......"
    }
    
    //有参闭包
    def caculate(Closure closure){
        def num1=1;
        def num2=3; 
        println("caculate start...")
        closure(num1,num2) 
        println("caculate end...")
    }
    caculate {x,y -> println "计算结果为:$x+$y=${x+y}"} //在build.gradle文件中我们见到的很多都是闭包格式的。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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 支持

    dependencies {
        testImplementation group: 'junit' ,name: 'junit', version: '4.12'
    }
    test {
        useJUnit()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Gradle 对于Junit5.x 版本支持

    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' 
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    }
    test {
        useJUnitPlatform()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:无论是 Junt4.x 版本还是Junit5.x 版本,我们只需在 build.gradle 目录下执行gradle test 指令,gradle 就会帮我们执行所有的加了@Test 注解的测试,并生成测试报告。

    gradle 在junit 中的批量测试,可以设置包含或者排除某些特定测试

    test {
        enabled true 
        useJUnit() 
        include 'com/**'
        exclude 'com/abc/**'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    三、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 识别,这样在构建的时候才会被包含进去

    //根工程项目名
    rootProject.name = 'root'
    //包含的子工程名称
    include 'subject01' 
    include 'subject02' 
    include 'subject03'
    //包含的子工程下的子工程名称
    include 'subject01:subproject011' 
    include 'subject01:subproject012'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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 执行之前执行。

    • 区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行

    task A {
        println "root taskA" 
        doFirst(){
            println "root taskA doFirst"
        }
        doLast(){
            println "root taskA doLast"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.2 任务的行为

    doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义

    def map=new HashMap<String,Object>();
    //action属性可以设置为闭包,设置task自身的行为
    map.put("action",{println "taskD.."})
    task(map,"a"){
        description   'taskA description  '
        group "atguigu"
        //在task内部定义doFirst、doLast行为
        doFirst {
            def name = 'doFirst..' 
            println name
        }
        doLast {
            def name = 'doLast..' 
            println name
        }
    }
    //在task外部定义doFirst、doLast行为
    a.doFirst {
        println it.description
    }
    a.doLast {
        println it.group
    }
    
    // 输出结果
    //taskA description 
    //doFirst..
    //taskD..
    //doLast..
    //atguigu
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    底层原理分析

    无论是定义任务自身的 action,还是添加的doLastdoFirst 方法,其实底层都被放入到一个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 任务的依赖

    // 参数方式依赖
    task A {
        doLast {
            println "TaskA.."
        }
    }
    task 'B' {
        doLast {
            println "TaskB.."
        }
    }
    //参数方式依赖: dependsOn后面用冒号
    task 'C'(dependsOn: ['A', 'B']) {
        doLast {
            println "TaskC.."
        }
    }}
    
    // =====================================
    
    
    //内部依赖
    //参数方式依赖
    task 'C' {
        //内部依赖:dependsOn后面用 = 号
        dependsOn= [A,B] 
        doLast {
            println "TaskC.."
        }
    }
    
    // =====================================
    
    //外部依赖:可变参数,引号可加可不加
    C.dependsOn(B,'A')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 当一个 Task 依赖多个Task 的时候,被依赖的Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响

    • 重复依赖的任务只会执行一次

    3.4 任务执行

    参考:https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_executing_tasks

    任务执行语法: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 方法

    task('A', {//任务名称,闭包都作为参数println "taskA..."
    })
    task('B') {//闭包作为最后一个参数可以直接从括号中拿出来println "taskB..."
    }
    task C {//groovy语法支持省略方法括号:上面三种本质是一种println "taskC..."
    }
    def map = new HashMap<String, Object>(); map.put("action", { println "taskD.." }) //action属性可以设置为闭包task(map,"D");
    tasks.create('E') {//使用tasks的create方法println "taskE.."
    }
    tasks.register('f') { //注:register执行的是延迟创建。也即只有当task被需要使用的时候才会被创建。
      println "taskF  "
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们也可以在定义任务的同时指定任务的属性

    • type:基于一个存在的Task来创建,和继承类似,默认值DefaultTask

    • overwrite:是否替换存在的Task,这个和type配合使用,默认值false

    • dependsOn:用户配置任务的依赖,默认值[]

    • action:添加到任务中的一个Action或者一个闭包,默认值null

    • description:用于配置任务的描述,默认值null

    • group:用于配置任务的分组,默认值null

    在定义任务时也可以给任务分配属性:定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性

    //①.F是任务名,前面通过具名参数给map的属性赋值,以参数方式指定任务的属性信息
    task(group: "atguigu", description: "this is task B", "F")
    //②.H是任务名,定义任务的同时,在内部直接指定属性信息
    task("H") {
        group("atguigu") description("this is the task H")
    }
    //③.Y是任务名,给已有的任务 在外部直接指定属性信息
    task "y" {}
    y.group = "atguigu"
    clean.group("atguigu") //案例:给已有的clean任务重新指定组信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.6 任务类型

    https://docs.gradle.org/current/dsl/index.html

    我们定义的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 目录删除

    tasks.register('myClean', Delete) { 
      delete buildDir
    }
    
    • 1
    • 2
    • 3

    自定义 Task 类型

    def myTask = task MyDefinitionTask(type: CustomTask) 
    myTask.doFirst() {
      println "task 执行之前 执行的 doFirst方法"
    }
    myTask.doLast() {
      println "task 执行之后 执行的 doLast方法"
    }
    
    class CustomTask extends DefaultTask {
    //@TaskAction表示Task本身要执行的方法@TaskAction
      def doSelf() {
        println "Task 自身 在执行的in doSelf"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.7 任务的执行顺序与动态分配

    官网:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

    在 Gradle 中, 有三种方式可以指定 Task 执行顺序:

    • dependsOn 强依赖方式

    • 通过 Task 输入输出

    • 通过 API 指定执行顺序

    gradle可以使用它在循环中注册同一类型的多个任务

    // 构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。
    4.times { counter ->
        tasks.register("task$counter") {
            doLast {
                println "I'm task number $counter"
            }
        }
    }
    tasks.named('task0') { dependsOn('task2', 'task3') }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.8 任务的关闭、开启与超时

    每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。如果 --continue 使用,其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时

    //每个任务都有一个 enabled 默认为的标志 true。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。
    task disableMe {
        doLast {
            println 'This task is Executing...'
        }
        enabled(true)//直接设置任务开启,默认值为true
    }
    //disableMe.enabled = false //设置关闭任务
    
    // ===========================
    
    //在控制台使用: gradle a b 测试会发现执行a 的时候,由于a 执行超时,抛出异常,所以没有继续往下执行【b 也没执行】。
    //然后在控制台使用: gradle a b –continue,测试会发现a 虽然执行失败,但是 b 还是执行了。
    task a() {
        doLast {
            Thread.sleep(1000)
            println "当前任务a执行了"
        }
        timeout = Duration.ofMillis(500)
    }
    task b() {
        doLast {
            println "当前任务b执行了"
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    3.9 任务的查找

    task atguigu {
        doLast {
            println "让天下没有难学的技术:尚硅谷"
        }
    }
    //根据任务名查找
    tasks.findByName("atguigu").doFirst({println "尚硅谷校区1:北京  "})
    tasks.getByName("atguigu").doFirst({println "尚硅谷校区2:深圳  "})
    //根据任务路径查找【相对路径】
    tasks.findByPath(":atguigu").doFirst({println "尚硅谷校区3:上海    "})
    tasks.getByPath(":atguigu").doFirst({println "尚硅谷校区4:武汉  "})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.10 任务的规则

    使用 gradle abc hello 进行测试,此时当 abc 任务不存在时,也不会报异常【不中断执行】而是提示自定义的规则信息,继续执行 hello 任务。此外,它还可以根据不同的规则动态创建需要的任务等情况

    task hello {
        doLast {
            println 'hello 尚硅谷的粉丝们'
        }
    }
    
    tasks.addRule("对该规则的一个描述,便于调试、查看等"){ String taskName -> task(taskName) {
        doLast {
            println "该${taskName}任务不存在,请查证后再执行"
        }
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.11 任务的 onlyIf 断言

    断言就是一个条件表达式。Task 有一个 onlyIf 方法。它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行, 否则跳过。这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,什么情况下执行单元测试的时候不执行网络测试等

    task hello {
        doLast {
            println 'hello 尚硅谷的粉丝们'
        }
    }
    
    hello.onlyIf { !project.hasProperty('fensi') }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试:通过-P 为Project 添加fensi 属性 gradle hello -Pfensi

    3.12 默认任务

    defaultTasks 'myClean', 'myRun' 
    tasks.register('myClean') {
        doLast {
            println 'Default Cleaning!'
        }
    }
    tasks.register('myRun') {
        doLast {
            println 'Default Running!'
        }
    }
    tasks.register('other') {
        doLast {
            println "I'm not a default task!"
        }
    }
    //gradle -q
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4、Gradle 中的文件操作

    4.1 本地文件

    使用 Project.file(java.lang.Object)方法,通过指定文件的相对路径或绝对路径来对文件的操作,其中相对路径为相对当前project**[根project 或者子project]**的目录。其实使用 Project.file(java.lang.Object)方法创建的 File 对象就是 Java 中的 File 对象,我们可以使用它就像在 Java 中使用一样

    //使用相对路径
    File configFile = file('src/conf.xml')
    configFile.createNewFile();
    // 使用绝对路径
    configFile = file('D:\\conf.xml')
    println(configFile.createNewFile())
    // 使用一个文件对象
    configFile = new File('src/config.xml')
    println(configFile.exists())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.2 文件集合

    文 件 集 合 就 是 一 组 文 件 的 列 表 , 在 Gradle 中 , 文 件 集 合 用 FileCollection 接 口 表 示 ,我 们 可 以 使 用 Project.files(java.lang.Object[])方法来获得一个文件集合对象

    def collection = files('src/test1.txt', new File('src/test2.txt'), ['src/test3.txt', 'src/test4.txt'])
    collection.forEach() { File it ->
        it.createNewFile() //创建该文件
        println it.name //输出文件名
    }
    Set set1 = collection.files // 把文件集合转换为java中的Set类型
    Set set2 = collection as Set
    List list = collection as List//  把文件集合转换为java中的List类型
    for (item in list) {
        println item.name
    }
    def union = collection + files('src/test5.txt') // 添加或者删除一个集合
    def minus = collection - files('src/test3.txt')
    union.forEach() { File it ->
        println it.name
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.3 文件树

    文件树是有层级结构的文件集合,一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。我们可以使用 Project.fileTree(java.util.Map)方法来创建文件树对象, 还可以使用过虑条件来包含或排除相关文件

    def tree = fileTree(dir: 'src/main', include: '**/*.java')
    tree = fileTree('src/main') {
        include '**/*.java'
    }
    tree = fileTree(dir: 'src/main', include: '**/*.java') //第三种方式:通过路径和闭包创建文件树:具名参数给map传值
    tree = fileTree(dir: 'src/main', includes: ['**/*.java', '**/*.xml', '**/*.txt'], exclude: '**/*test*/**')
    tree.each { File file -> // 遍历文件树的所有文件
        println file
        println file.name
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.4 文件拷贝

    我们可以使用 Copy 任务来拷贝文件,通过它可以过虑指定拷贝内容,还能对文件进行重命名操作等。Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,这里使用CopySpec.from(java.lang.Object[])方法指定原文件;使用CopySpec.into(java.lang.Object)方法指定目标目录

    task copyTask(type: Copy) {
        from 'src/main/resources'
        into 'build/config'
    }
    
    • 1
    • 2
    • 3
    • 4

    from()方法接受的参数和文件集合时files()一样。当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝);当参数为一个文件时,该文件会被拷贝到指定目录;如果参数指定的文件不存在,就会被忽略; 当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。

    task copyTask(type: Copy) {
    // 拷贝src/main/webapp目录下所有的文件
        from 'src/main/webapp'
    // 拷贝单独的一个文件
        from 'src/staging/index.html'
    // 从Zip压缩文件中拷贝内容
        from zipTree('src/main/assets.zip')
    // 拷贝到的目标目录
        into 'build/explodedWar'
    }
    
    // 在拷贝文件的时候还可以添加过虑条件来指定包含或排除的文件
    task copyTaskWithPatterns(type: Copy) {
        from 'src/main/webapp'
        into 'build/explodedWar'
        include '**/*.html'
        include '**/*.jsp'
        exclude { details -> details.file.name.endsWith('.html') }
    }
    
    // 在拷贝文件的时候还可以对文件进行重命名操作
    task rename(type: Copy) {
        from 'src/main/webapp'
        into 'build/explodedWar'
    // 使用一个闭包方式重命名文件
        rename { String fileName ->
            fileName.replace('-staging-', '')
        }
    }
    
    // 拷贝
    task copyMethod {
        doLast {
            copy {
                from 'src/main/webapp'
                into 'build/explodedWar'
                include '**/*.html'
                include '**/*.jsp'
            }
        }
    }
    // 执行gradle build即可
    copy {
    //相对路径或者绝对路径
        from file('src/main/resources/ddd.txt') //file也可以换成new File()
        into this.buildDir.absolutePath
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    4.5 文件归档

    官方文档:https://docs.gradle.org/current/userguide/working_with_files.html

    通常一个项目会有很多的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
    
    // 使用 Project.zipTree(java.lang.Object)和 Project.tarTree(java.lang.Object)方法来创建访问 Zip 压缩包的文件树对象
    // 使用zipTree
    FileTree zip = zipTree('someFile.zip')
    // 使用tarTree
    FileTree tar = tarTree('someFile.tar')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5、Dependencies

    5.1 依赖方式

    Gradle 中的依赖分别为直接依赖,项目依赖,本地jar 依赖

    dependencies {
      //依赖当前项目下的某个模块[子工程]
      implementation project(':subject01')
      //直接依赖本地的某个jar文件
      implementation files('libs/foo.jar', 'libs/bar.jar')
      //配置某文件夹作为依赖项
      implementation fileTree(dir: 'libs', include: ['*.jar'])
      //直接依赖
      implementation 'org.apache.logging.log4j:log4j:2.17.2'
      testImplementation 'org.springframework.boot:spring-boot-starter-test'
      //完整写法
      //implementation group: 'org.apache.logging.log4j', name: 'log4j', version: '2.17.2'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    当执行 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 也提供了依赖的类型

    compileOnlyjava插件提供,曾短暂的叫provided,后续版本已经改成了compileOnly,适用于编译期需要而不需要打包的情 况
    runtimeOnly由 java 插件提供,只在运行期有效,编译时不需要,比如mysql 驱动包。取代老版本中被移除的 runtime
    implementation由 java 插件提供,针对源码[src/main 目录] ,在编译、运行时都有效,取代老版本中被移除的 compile
    testCompileOnlyjava 插件提供,用于编译测试的依赖项,运行时不需要
    testRuntimeOnlyjava 插件提供,只在测试运行时需要,而不是在测试编译时需要,取代老版本中被移除的testRuntime
    testImplementation由 java 插件提供,针对测试代码[src/test 目录] 取代老版本中被移除的testCompile
    providedCompilewar 插件提供支持,编译、测试阶段代码需要依赖此类jar 包,而运行阶段容器已经提供了相应的支持,所 以无需将这些文件打入到war 包中了;例如servlet-api.jar、jsp-api.jar
    compile编译范围依赖在所有的 classpath 中可用,同时它们也会被打包。在gradle 7.0 已经移除
    runtimeruntime 依赖在运行和测试系统的时候需要,在编译的时候不需要,比如mysql 驱动包。在 gradle 7.0 已经移除
    apijava-library 插件提供支持,这些依赖项可以传递性地导出给使用者,用于编译时和运行时。取代老版本中被 移除的 compile
    compileOnlyApijava-library 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。

    5.3 api 与implementation 区别

    apiimplementation
    编译时能进行依赖传递,底层变,全部都要变、编译速度慢不能进行依赖传递,底层变不用全部都要变、编译速度快
    运行时运行时会加载,所有模块的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移除一个依赖,不允许依赖传递,强制使用某个版本

    //Exclude 排除某个依赖
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        implementation('org.hibernate:hibernate-core:3.6.3.Final'){
            //排除某一个库(slf4j)依赖:如下三种写法都行
            exclude group: 'org.slf4j'
            exclude module: 'slf4j-api'
            exclude group: 'org.slf4j',module: 'slf4j-api'
        }
        //排除之后,使用手动的引入即可。
        implementation 'org.slf4j:slf4j-api:1.4.0'
    }
    
    //不允许依赖传递
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        implementation('org.hibernate:hibernate-core:3.6.3.Final'){
            //不允许依赖传递,一般不用
            transitive(false)
        }
         //排除之后,使用手动的引入即可
        implementation 'org.slf4j:slf4j-api:1.4.0'
    }
    
    //强制使用某个版本
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        implementation('org.hibernate:hibernate-core:3.6.3.Final')
        //强制使用某个版本!!【官方建议使用这种方式】
        implementation('org.slf4j:slf4j-api:1.4.0!!')
        //这种效果和上面那种一样,强制指定某个版本
        implementation('org.slf4j:slf4j-api:1.4.0'){
            version{
                strictly("1.4.0")
            }
        }
    }
    
    // 我们可以先查看当前项目中到底有哪些依赖冲突
    //下面我们配置,当Gradle 构建遇到依赖冲突时,就立即构建失败
    configurations.all() {
        Configuration configuration ->
            //当遇到版本冲突时直接构建失败
            configuration.resolutionStrategy.failOnVersionConflict()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    6、Gradle 插件

    6.1 插件介绍

    • 促进代码重用、减少功能类似代码编写、提升工作效率

    • 促进项目更高程度的模块化、自动化、便捷化

    • 可插拔式的的扩展项目的功能

    在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:

    • 可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等

    • 可以添加依赖配置到项目中。

    • 可以向项目中拓展新的扩展属性、方法等。

    • 可以对项目进行一些约定,如应用Java 插件后,约定src/main/java 目录是我们的源代码存在位置,编译时编译这个目录下的Java 源代码文件

    6.2 插件的分类和使用

    脚本插件

    脚本插件的本质就是一个脚本文件,使用脚本插件时通过apply from:将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用。

    脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用,比如:将很多共有的库版本号一起管理、应用构建版本一起管理等

    //version.gradle文件
    ext {
        company= "尚硅谷"
        cfgs = [
                compileSdkVersion : JavaVersion.VERSION_1_8
        ]
        spring = [
                version : '5.0.0'
        ]
    }
    
    //build.gradle文件
    //map作为参数,可以直接引入
    apply from: 'version.gradle'
    task taskVersion{
        doLast{
            println "公司名称为:${company},JDK版本是${cfgs.compileSdkVersion},版本号是${spring.version}"
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    对象插件之内部插件[核心插件]

    https://docs.gradle.org/current/userguide/plugin_reference.html

    二进制插件[对象插件]就是实现了org.gradle.api.Plugin 接口的插件,每个Java Gradle 插件都有一个plugin id

    // 使用plugins DSL 方式
    // 如果是第三方插件已经被托管在https://plugins.gradle.org/
    plugins {
        id 'org.springframework.boot' version '2.7.5'
        id 'java'
    }
    
    //也可以使用闭包作为project.apply方法的一个参数
    apply{
        plugin 'java'
    }
    
    //使用方式1:Map具名参数,全类名
    apply plugin:org.gradle.api.plugins.JavaPlugin
    //org.gradle.api.plugins默认导入:使用方式2
    apply plugin:JavaPlugin
    apply plugin: 'java' //核心插件,无需事先引入,使用方式3:插件的id
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第三方插件

    //使用传统的应用方式
    buildscript {
        ext {
            springBootVersion = "2.3.3.RELEASE"
        }
        repositories {
            mavenLocal()
            maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
            jcenter()
        }
    // 此处先引入插件
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    //再应用插件
    apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,不必写版本号
    
    // 使用plugins DSL 方式
    // 如果是第三方插件已经被托管在https://plugins.gradle.org/
    plugins {
        id 'org.springframework.boot' version '2.7.5'
        id 'java'
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    用户自定义插件

    参考地址:https://docs.gradle.org/current/userguide/custom_plugins.html

    我们直接执行hello 任务./gradle hello 即可,这种方式实现的插件我们一般不使用,因为这种方式局限性太强,只能本Project,而其他的Project 不能使用

    interface GreetingPluginExtension {
        Property<String> getMessage()
        Property<String> getGreeter()
    }
    class GreetingPlugin implements Plugin<Project> {
        void apply(Project project) {
            def extension = project.extensions.create('greeting', GreetingPluginExtension)
            project.task('hello') {
                doLast {
                    println "${extension.message.get()} from ${extension.greeter.get()}"
                }
            }
        }
    }
    apply plugin: GreetingPlugin
    // Configure the extension using a DSL block
    greeting {
        message = 'Hi'
        greeter = 'Gradle'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    6.3 buildSrc 项目

    buildSrc 是Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。

    • 首先先建立一个名为** buildSrc 的 java Module**,将 buildSrc 从 included modules 移除,重新构建,然后只保留 build.gradle和src/main 目录,其他全部删掉,注意名字一定是 buildSrc,不然会找不到插件

    • 然后修改build.gradle中的内容

    apply 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'
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    然后实现插件代码Text.groovy,注意文件后缀为groovy,文件要引入package com.atguigu

    package 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插件")
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    接下来在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 仓库中

    apply plugin: 'groovy' //必须
    apply plugin: 'maven-publish'
    dependencies {
        implementation gradleApi() //必须
        implementation localGroovy() //必须
    }
    repositories {
        google()
        jcenter()
        mavenCentral() //必须
    }
    sourceSets { //把项目入口设置为src/main/groovy
        main {
            groovy {
                srcDir 'src/main/groovy'
            }
        }
    }
    publishing {
        publications {
            myLibrary(MavenPublication) {
                groupId = 'com.atguigu.plugin' //指定GAV坐标信息
                artifactId = 'library'
                version = '1.1'
                from components.java//发布jar包
    //from components.web///引入war插件,发布war包
            }
        }
        repositories {
            maven { url "$rootDir/lib/release" }
    //发布项目到私服中
    // maven {
    // name = 'myRepo' //name属性可选,表示仓库名称,url必填
    // //发布地址:可以是本地仓库或者maven私服
    // //url = layout.buildDirectory.dir("repo")
    // //url='http://my.org/repo'
    // // change URLs to point to your repos, e.g. http://my.org/repo
    // //认证信息:用户名和密码
    // credentials {
    // username = 'joe'
    // password = 'secret'
    // }
    // }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 执行publish 指令,发布到根project 或者maven 私服仓库(这里发布到了本地)

    • 使用插件,在项目级build.gradle 文件中将插件添加到classpath:

    buildscript {
        repositories {
            maven { url "$rootDir/lib/release" }
        }
        dependencies {
            classpath "com.atguigu.plugin:library:1.1"
        }
    }
    apply plugin: 'java'
    //是在atguiguplugin 中定义的插件ID
    apply plugin: 'com.atguigu.plugin'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 执行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 解决业务代码与测试代码中文乱码问题

    //指定使用什么版本的JDK语法编译源代码,跟编译环境有关,在有java插件时才能用
    sourceCompatibility = 1.8
    //指定生成特定于某个JDK版本的class文件:跟运行环境有关,在有java插件时才能用
    targetCompatibility = 1.8
    //业务编码字符集,注意这是指定源码解码的字符集[编译器]
    compileJava.options.encoding "UTF-8"
    //测试编码字符集,注意这是指定源码解码的字符集[编译器]
    compileTestJava.options.encoding "UTF-8"
    //编译JAVA文件时采用UTF-8:注意这是指定源码编码的字符集【源文件】
    tasks.withType(JavaCompile) {
        options.encoding = "UTF-8"
    }
    //编译JAVA文件时采用UTF-8:注意这是指定文档编码的字符集【源文件】
    tasks.withType(Javadoc) {
        options.encoding = "UTF-8"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7.2 Repositories

    Gradle 没有自己的远程仓库,而是使用Maven、jcenter、jvy、google 这些远程仓库

    repositories {
        //gradle中会按着仓库配置的顺序,从上往下依次去对应的仓库中找所需要的jar包:
        //如果找到,则停止向下搜索,如果找不到,继续在下面的仓库中查找
        //指定去本地某个磁盘目录中查找:使用本地file文件协议:一般不用这种方式
        maven { url 'file:///D:/repos/mavenrepos3.5.4'} maven { url "$rootDir/lib/release" }
        //指定去maven的本地仓库查找
        mavenLocal()
        //指定去maven的私服或者第三方镜像仓库查找
        maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" } maven { name "Bstek" ; url "https://nexus.bsdn.org/content/groups/public/" }
        //指定去maven的远程仓库查找:即 https://repo.maven.apache.org/maven2/
        mavenCentral()
        //去google仓库查找google()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    7.3 Subprojects 与 Allprojects

    **allprojects 是对所有project(包括Root Project+ child Project[当前工程和所有子工程])的进行统一配置,而subprojects **是对所有Child Project 的进行统一配置

    allprojects {
        tasks.create('hello') {
            doLast {
                task ->
                    println "project name is $task.project.name"
            }
        }
    }
    subprojects {
        hello.doLast{
            task->
                println "here is subprojects $task.project.name"
        }
    }
    
    // 在对单个Project 进行单独配置
    project('subject01') {
        task subject01 {
            doLast {
                println 'for subject01'
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    7.4 ext 用户自定义属性

    详细请参考:https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties

    Project 和Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的ext 属性即可实现。添加之后可以通过ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过ext 代码块

    //自定义一个Project的属性
    ext.age = 18
    //通过代码块同时自定义多个属性
    ext {
        phone = 19292883833
        address="北京尚硅谷"
    }
    task extCustomProperty {
    //在task中自定义属性
        ext {
            desc = "奥利给"
        }
        doLast {
            println "年龄是:${age}"
            println "电话是:${phone}"
            println "地址是:${address}"
            println "尚硅谷:${desc}"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    ext 配置的是用户自定义属性,而gradle.properties 中一般定义系统属性、环境变量、项目属性、JVM 相关配置信息。例如gradle.properties 文件案例:加快构建速度的,gradle.properties 文件中的属性会自动在项目运行时加载。

    ## 设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出
    org.gradle.jvmargs=-Xms4096m -Xmx8192m
    ## 开启gradle缓存
    org.gradle.caching=true
    #开启并行编译
    org.gradle.parallel=true
    #启用新的孵化模式
    org.gradle.configureondemand=true
    #开启守护进程
    org.gradle.daemon=true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    7.5 Buildscript

    buildscript 里是gradle 脚本执行所需依赖,分别是对应的 maven 库和插件

    • buildscript{}必须在build.gradle 文件的最前端

    • 对于多项目构建,项目的buildscript ()方法声明的依赖关系可用于其所有子项目的构建脚本

    • 构建脚本依赖可能是Gradle 插件

    import org.apache.commons.codec.binary.Base64
    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
        }
    }
    tasks.register('encode') {
        doLast {
            def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
            println new String(encodedString)
        }
    }
    
    
    
    //老式apply插件的引用方式,使用apply+buildscript
    buildscript {
        ext {
            springBootVersion = "2.3.3.RELEASE"
        }
        repositories {
            mavenLocal()
            maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
            jcenter()
        }
    //此处引入插件
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    apply plugin: 'java' //核心插件,无需事先引入
    apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,才能应用,不必写版本号
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    8、项目发布

    plugins {
        id 'java-library' //如果发布war包,需要war插件,java-library支持带源码、文档发布
        id 'maven-publish'
    }
    
    
    //带源码和javadoc的发布:需要'java-library'插件支持:它是java的升级版,java插件的功能java-library都有
    //javadoc.options.encoding="UTF-8"
    //java {
    // withJavadocJar()
    // withSourcesJar()
    //}
    publishing {
        publications {
            myLibrary(MavenPublication) {
                groupId = 'org.gradle.sample' //指定GAV坐标信息
                artifactId = 'library'
                version = '1.1'
                from components.java//发布jar包
                 //from components.web///引入war插件,发布war包
            }
        }
        repositories {
            //本地仓库位于USER_HOME/.m2/repository
            mavenLocal()
            //发布项目到私服中
            maven {
                name = 'myRepo' //name属性可选,表示仓库名称,url必填
                //发布地址:可以是本地仓库或者maven私服
                //url = layout.buildDirectory.dir("repo")
                // change URLs to point to your repos, e.g. http://my.org/repo
                def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
                def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
                url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
                //认证信息:用户名和密码
                // credentials {
                // username = 'joe'
                // password = 'secret'
                // }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    执行发布命令,将项目发布到本地仓库或者远程仓库。常见的发布指令有:

    • 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 方法。

    gradle.settingsEvaluated { //1.settingsEvaluated钩子函数,在初始化阶段完成
        println "settingsEvaluated"
    }
    gradle.projectsLoaded { //2.projectsLoaded钩子函数,在初始化阶段完成
        println "projectsLoaded"
    }
    //声明一个变量:表示当前项目名,在每次执行某个项目的beforeEvaluate方法时先给projectName变量赋值
    //这样方便在:gradle.beforeProject和afterProject两个钩子函数使用。
    def projectName=""
    gradle.addProjectEvaluationListener( new ProjectEvaluationListener(){
    //3.执行各个project的beforeEvaluate:在配置阶段完成
        @Override
        void beforeEvaluate(Project project) {
            projectName=project.name
            println "${project.name} Project beforeEvaluate"
        }
    //5.执行各个project的afterEvaluate:在配置阶段完成
        @Override
        void afterEvaluate(Project project, ProjectState projectState) {
            println "${project.name} Project afterEvaluate"
        }
    });
    gradle.beforeProject {//4.执行各个project的beforeProject:在配置阶段完成
        println "${projectName} beforeProject..."
    }
    gradle.afterProject {//6.执行各个project的afterProject:在配置阶段完成
        println "${projectName} afterProject..."
    }
    //7.所有工程的build.gradle 执行完毕后,回调Gradle 对象的projectsEvaluated 方法:在配置阶段完成
    def rootProjectName=rootProject.getName()
    gradle.projectsEvaluated {
        println "${rootProjectName} projectsEvaluated..."
    }
    //8.配置阶段完毕后,回调TaskExecutionGraph 对象的whenReady 方法:在配置阶段完成
    gradle.taskGraph.whenReady {
        println "${rootProjectName} taskGraph whenReady..."
    }
    //9.在当前Task执行之前,会回调TaskExecutionGraph 对象的beforeTask方法:在执行阶段完成
    gradle.taskGraph.beforeTask {task ->
        println "this is the task ${task.name} of the project ${task.getProject().name} beforeTask.."
    }
    //10.在当前Task执行之后,会回调TaskExecutionGraph 对象的afterTask方法:在执行阶段完成
    gradle.taskGraph.afterTask {task ->
        println "this is the task ${task.name} of the project ${task.getProject().name} afterTask.."
    }
    //11.当所有的Task 执行完毕后,会回调Gradle 对象的buildFinish 方法:在执行阶段完成
    gradle.buildFinished {
        println "${rootProjectName} buildFinished..."
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    9.2 生命周期扩展

    settings.gradle 中添加监听器,查看task 有向无环图

    gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
        @Override //生成有向无环图
        void graphPopulated(TaskExecutionGraph taskExecutionGraph) {
            taskExecutionGraph.allTasks.forEach(task->{//核心逻辑:通过taskExecutionGraph获得所有的task
                taskExecutionGraph.allTasks.forEach(releaseTask->{
                    println "尚硅谷:" + releaseTask.getProject().name + ":" + releaseTask.name
                })
            })
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    计算Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的settings 文件,并没有计算init.gradle 初始化的时间

    def projectName=rootProject.getName() //定义项目名
    long beginOfSetting = System.currentTimeMillis() //初始化阶段开始时间
    def beginOfConfig //配置阶段开始时间
    def configHasBegin = false //配置阶段是否开始了,只执行一次
    def beginOfProjectConfig = new HashMap() //存放每个build.gradle 执行之前的时间
    def beginOfTaskExecute //执行阶段开始时间
    gradle.projectsLoaded { //初始化阶段执行完毕
        println "${projectName}工程初始化总耗时${System.currentTimeMillis() - beginOfSetting} ms"
    }
    //build.gradle 执行前
    gradle.beforeProject {Project project ->
        if(!configHasBegin){
            configHasBegin = true
            beginOfConfig = System.currentTimeMillis()
        }
        beginOfProjectConfig.put(project,System.currentTimeMillis())
    }
    //build.gradle 执行后
    gradle.afterProject {Project project ->
        def begin = beginOfProjectConfig.get(project)
        if(project.name == projectName) {
            println "根工程${projectName} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
        }else{
            println "子工程${project.name} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
        }
    }
    gradle.taskGraph.whenReady {//配置阶段完毕
        println "整个${projectName}项目在配置阶段总耗时:${System.currentTimeMillis() - beginOfConfig} ms"
        beginOfTaskExecute = System.currentTimeMillis()
    }
    //执行阶段开始
    gradle.taskGraph.beforeTask {Task task ->
        task.doFirst {
            task.ext.beginOfTask = System.currentTimeMillis()
        }
        task.doLast {
            println "${task.name}在执行阶段耗时:${System.currentTimeMillis() - task.ext.beginOfTask} ms"
        }
    }
    gradle.buildFinished {//执行阶段完毕
        println " 执行阶段总耗时:${System.currentTimeMillis() - beginOfTaskExecute} ms"
        println " 整个构建过程耗时:${System.currentTimeMillis() - beginOfSetting} ms"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    四、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脚手架会直接配置好,推荐)

    plugins {
        //维护springboot版本号,不单独使用,和下面两个插件一起用
        id 'org.springframework.boot' version '2.3.7.RELEASE' 
        // 统一管理版本号,下面依赖就不需要写版本了
        id 'io.spring.dependency-management' version '1.0.10.RELEASE'
        //进行依赖管理,在引入其它boot依赖时省略版本号、解决jar包冲突问题
        id 'java'
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        implementation 'org.springframework.boot:spring-boot-starter-web' //省略版本,原生bom支持,插件management提供
        testImplementation('org.springframework.boot:spring-boot-starter-test') {
            exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
        }
    }
    test {
        useJUnitPlatform()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.2 程序运行与打包

    要想运行当前Springboot 项目,直接执行gradle bootRun 指令或者idea 右侧按钮即可。当然如果想让当前项目打成可执行jar 包,只需执行: gradle bootJar 指令即可。

    Cloud 项目创建也可以借助于脚手架创建,与Boot 项目类似。

    1.3 拓展spring-boot-gradle-plugin 插件

    buildscript {
        repositories {
            maven { url 'https://maven.aliyun.com/repository/public' }
        }
        dependencies {
            classpath 'org.springframework.boot:spring-boot-gradle-plugin:2.4.1'
        }
    }
    // 这样就不用自己管理版本号了
    apply plugin: 'org.springframework.boot'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.4 全局属性管理

    有时我们在 gradle 里定义了一些全局属性,想在 springboot 的 application 配置文件里使用,甚至可以给多个模块公用一个全局属性

    首先在根目录build.gradle进行配置,首先需要暴露属性

    // 将 gradle 的配置应用于yml配置文件
    processResources {
        filesMatching('application.yml') {
            expand(project.properties)
        }
    }
    
    // 将 gradle 的配置应用于所有 springboot 配置文件
    processResources {
        expand(project.properties)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后在根目录创建例如gradle.properties

    author=shawn
    age=18
    
    • 1
    • 2

    最后在项目的application引入即可,banner也可以按此方法引入

    info:
      mine:
        name: ${author}
        age: ${age}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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文件,该文件一个项目只能存在一个,用于配置项目的父子关系

    rootProject.name = 'microservice'
    include 'microservice_bean'
    include 'microservice_common'
    include 'microservice_gateway'
    include 'microservice_service'
    include 'microservice_service:service_user'
    // 可以设置别名
    findProject(':microservice_service:service_user')?.name = 'service_user'
    include 'microservice_service:service_order'
    findProject(':microservice_service:service_order')?.name = 'service_order'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后在根目录创建version.gradle文件,用户自定义用户属性和版本号

    // 依赖版本管理
    ext {
        version = [
                "fastjsonVersion"   : "1.2.72",
                "mybatisPlus" : "3.0.5",
                "mysql" : "5.1.46",
                "swaggerVersion": "2.7.0",
                "jjwtVersion": "0.7.0"
        ]
    
        dependencies = [
                "fastjson"                      : "com.alibaba:fastjson:${version.fastjsonVersion}",
                "mybatis-plus-boot-starter"     : "com.baomidou:mybatis-plus-boot-starter:${version.mybatisPlus}",
                "mysql"                         : "mysql:mysql-connector-java:${version.mysql}",
                "swagger"                       : "io.springfox:springfox-swagger2:${version.swaggerVersion}",
                "swaggerUI"                     : "io.springfox:springfox-swagger-ui:${version.swaggerVersion}",
                "jjwt"                          : "io.jsonwebtoken:jjwt:${version.jjwtVersion}"
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    接下来build.gradle是父工程的全局配置管理中心,一般在这里设置版本号等信息,然后子工程进行继承,这样子工程就不需要写版本号了。

    description 'gradle微服务实战父工程'
    
    //构建Gradle脚本自身需要的资源,可以声明的资源包括依赖项、第三方插件、maven仓库地址等。
    buildscript {
        ext {
            // 自定义扩展 字段 这里定义版本信息
            //定义一个变量,统一规定springboot的版本
            springBootVersion = '2.2.1.RELEASE'
            springCloudVersion = 'Hoxton.RELEASE'
            springCloudAlibabaVersion = '0.2.2.RELEASE'
        }
    
        //设置仓库
        repositories {
            maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
            maven { url 'https://repo.spring.io/milestone'}
        }
    
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    
    //配置全局, 包括root项目和子项目
    allprojects {
        group 'com.shawn'
        version '1.0-SNAPSHOT'
    
    
        //配置编码格式
        tasks.withType(JavaCompile) {
            options.encoding = "UTF-8"
        }
    
        //设置仓库
        repositories {
            mavenLocal()
            maven { url 'https://maven.aliyun.com/nexus/content/groups/public/' }
            maven { url 'https://repo.spring.io/milestone'}
            mavenCentral()
        }
    
    }
    
    // 导入自定义的用户属性文件
    apply from: 'version.gradle'
    
    //配置所有子项目
    subprojects {
        // 使用二进制插件 
        apply plugin: 'java'
        apply plugin: 'java-library' // 可以使用api编译
        //apply plugin: 'idea' // 让Gradle自动生成Intellij的项目文件
        // 依赖管理插件仍然是一个spring-boot-gradle-plugin传递依赖,所以无需在build.gradle配置中明确列出此依赖。
        apply plugin: 'io.spring.dependency-management' //依赖管理,用来传递spring的依赖
    
        sourceCompatibility= JavaVersion.VERSION_1_8
        targetCompatibility=  JavaVersion.VERSION_1_8
    
    
        //公用的依赖
        dependencies {
            testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
            testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        }
    
        test {
            useJUnitPlatform()
        }
        
        // 可以参考:https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/
    
        // 默认bootJar和bootWar is true
        // jar和war 是false
        // springboot打出的bootJar包  一般是 不能被依赖的
        // 可以参考:https://www.cnblogs.com/karlMa/p/11304524.html
        // 打出来的jar包可以子模块间相互依赖,但经过个人测试后发现不可以被执行
        // 如果不开启的话,后续是会出现模块间找不到依赖的问题!
        jar.enabled = true
        // jar名字带boot
        bootJar {
            archiveClassifier = 'boot'
        }
    
        group = 'com.example'
        version = '0.0.1-SNAPSHOT' /* 项目版本,会加在打包文件名中 */
    
        // dependencyManagement版本统一管理,类似于父maven的dependencyManagement
        // 此时只是一个依赖管理,并不会导入任何的jar包
        // 子项目需要的时候才会真正导入
        dependencyManagement {
        
            // 循环导入自定义配置第三方依赖
            dependencies {
                for(depJar in rootProject.ext.dependencies){
                    dependency depJar.value
                }
            }
            // 导入SpringBoot和SpringCloud依赖Bom
            imports {
                mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
                mavenBom "org.springframework.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
            }
        }
    }
    
    // 根目录下的xx项目
    project(':microservice_bean'){
        description("微服务实战之bean层:存放表对应的实体类")
    }
    
    project(":microservice_common"){
        description("微服务实战之公共模块:存放微服务常用的工具类")
        //依赖
        dependencies {
            // api可以传递,编译速度慢,implement不可以传递,编译速度快
            api 'com.alibaba:fastjson'
            api 'mysql:mysql-connector-java'
            api 'com.baomidou:mybatis-plus-boot-starter'
            api 'io.springfox:springfox-swagger2'
            api 'io.springfox:springfox-swagger-ui'
            api 'io.jsonwebtoken:jjwt'
    
            api 'org.springframework.cloud:spring-cloud-starter-openfeign'
            api 'org.springframework.cloud:spring-cloud-starter-alibaba-sentinel'
            api 'org.springframework.cloud:spring-cloud-starter-alibaba-nacos-discovery'
    
        }
    }
    
    project(":microservice_service"){
        description("微服务实战之服务模块:存放各个微服务模块")
        apply plugin: 'org.springframework.boot'
    
        subprojects {
            apply plugin : 'java-library'
            apply plugin: 'org.springframework.boot'
    
            dependencies {
                api 'org.springframework.boot:spring-boot-starter-web'
                api project(':microservice_bean')
                api project(':microservice_common')
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    2.3 配置公共子模块

    创建bean和common模块

    // jar包后的名字,根据需要自定义
    archivesBaseName = 'common'
    // 因为它没有主程序入口,也不需要它达成springboot的jar包
    bootJar.enabled = false
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4 配置Service服务类

    首先创建service模块服务,下面在分别创建order和user服务,因为service亚父工程不需要jar包,所以在build.gradle配置,同时删除src等其他非必须文件

    // 不需要打可执行jar包
    bootJar {
        enabled = false
    }
    
    • 1
    • 2
    • 3
    • 4

    在子模块的build.gradle中进行配置,此时根据需要进行依赖的引入,不过此时不需要进行版本号的编写,版本号统一由父工程进行管理;下面的文件因为版本依赖等已经由父工程配置了,因此无需额外配置

    archivesBaseName = 'order'
    plugins {
        id 'java'
    }
    
    group 'com.shawn'
    version '1.0-SNAPSHOT'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
        //implementation 'org.springframework.boot:spring-boot-starter-web'
    }
    
    test {
        useJUnitPlatform()
    }
    
    //bootJar {
    //    archiveClassifier = 'boot'
    //}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    最后如果需要将其他模块的bean加入Spring管理,注意在主启动类添加包扫描路径@ComponentScan("com.shawn"),最后在对于的目录下进行打包gradle build,或者IDEA点击bootJar也是打包可执行Jar包(非jar按钮)


    参考

    https://www.bilibili.com/video/BV1yT41137Y7

    https://blog.csdn.net/qyb19970829/article/details/110649629

  • 相关阅读:
    不讲故事的设计模式-责任链模式
    Redis的哨兵机制
    C语言进阶—深度剖析数据在内存中的存储
    【分布式搜索引擎es】
    RNN模型参数与变量的依赖以及时间反向传播梯度的推导
    经验终结:arduino 环境下,esp8266 定时器的使用说明
    了解web框架
    CAD插件的安装和自动加载dll、arx
    代码随想录day42|背包问题基础|416. 分割等和子集|Golang
    bugku-web-社工-初步收集
  • 原文地址:https://blog.csdn.net/lemon_TT/article/details/127577218