Gradle 基础篇之基础知识的介绍和使用

1. 项目结构

目录介绍:

  1. build.gradle:项目编译时要读取的配置文件,build.gradle有两个,一个是全局的,一个是在模块里面。全局的build.gradle主要设置的是声明仓库源,gradle的版本号说明等。
  2. gradlew:linux下的gradle环境脚本。可以执行gradle指令,比如./greadle build。
  3. gradlew.bat:windows下的gradle环境脚本。可以执行gradle指令。
  4. settings.gradle:包含一些必要设置,例如,任务或项目之间的依赖关系等,无论有多少子模块,该文件都只有一个,且在根目录中。
  5. gradle:包含wrapper文件夹及其两个子文件,可以自动安装gradle环境。也就是如果本地没有安装gradle环境,则可以使用这里的。

Gradle-Wrapper是为了简化Gradle的安装和部署,目的是为了使任意的gradle项目都不需要单独安装环境,项目会自动识别有无gradle环境。如果在本地没有找到与gradle-wrapper.properties中版本相同的Gradle。IDEA就会自动帮你下载一个gradle环境。gradle/wrapper/gradle-wrapper.properties内容解析:

properties 复制代码
#greadistributionBase和distributionPath配合使用,指定gradle解压后的存放位置。
#GRADLE_USER_HOME表示用户目录,可以在环境变量中配置,如果未配置,则使用默认的:
    #windows系统默认是:C:\window\<user-name>\.gradle\
    #linux系统:$HOME/.gradle
greadistributionBase=GRADLE_USER_HOME   
distributionPath=wrapper/dists

#指定某个版本的gradle的下载地址。
distributionUrl=https://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true

#zipStoreBase和zipStorePath配合使用,指定下载的gradle.zip文件的存放位置。
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

这里的 包装器 指的就是使用gradle-wrapper.properties指定的gradle来构建项目。

Gradle遵循COC(约定优于配置)的理念,默认情况下提供了与Maven项目相同的项目结构配置:

java 复制代码
project root
src/main/java
src/main/resource
src/test/java
src/test/resource
src/main/webapp(web工程)

2. 常用命令

执行 gradle build 构建项目,会产生一个build目录,里面有打包好的jar包。

执行 gradle build -x test 跳过测试构建项目。

bin 复制代码
PS D:\Project\gradle-demo> gradle build
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

BUILD SUCCESSFUL in 12s
7 actionable tasks: 7 executed
PS D:\Project\gradle-demo>

执行 gradle clean 命令会删除build目录。

bin 复制代码
PS D:\Project\gradle-demo> gradle clean

BUILD SUCCESSFUL in 800ms
1 actionable task: 1 executed
PS D:\Project\gradle-demo> 

执行 gradle -v 可以查看版本,用的是本地安装的gradle,配置环境变量的那个gradle。

bin 复制代码
PS D:\Project\gradle-demo> gradle -v

执行 ./gradlew -v 这里用的是项目中wrapper下的那个gradle。

bin 复制代码
PS D:\Project\rcp> ./gradlew -v

执行 gradle -h 可以查看帮助。

bin 复制代码
PS D:\Project\gradle-demo> gradle -h

3. build.gradle

java 复制代码
plugins {
    id 'java'
    id 'org.springframework.boot' version '4.0.0'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
description = 'gradle-demo'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    //配置国内下载源
    maven{
        url 'https://maven.aliyun.com/repository/public/'
    }
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}
  1. group、name、version:group是module所在组;name是module的名字,同一个组里name具有唯一性;version是module的版本号;group、name、version三者构成了该module的唯一性。
  2. apply :在module中应用一个插件。和plugins一个意思。
  3. dependencies:用来声明module所依赖的jar包或其他module。
  4. repositories:用来声明仓库,告诉程序到哪个仓库去查找对应的module、jar包等依赖。
  5. task:用来声明module的任务,其对应org.gradle.api.Task。

依赖管理

Gradle依赖的粒度控制相较于Maven也更加精细,maven只有compile、provided、test、runtime四种scope,而gradle有以下几种scope:

  1. implementation:默认的scope。implementation的作用域会让依赖在编译和运行时均包含在内,但是不会暴露在类库使用者的编译时。举例,如果我们的类库包含了gson,那么其他人使用我们的类库时,编译时不会出现gson的依赖。
  2. api:和implementation类似,都是编译和运行时都可见的依赖。但是api允许我们将自己类库的依赖暴露给我们类库的使用者。
  3. compileOnlyruntimeOnly:这两种顾名思义,一种只在编译时可见,一种只在运行时可见,而runtimeOnly和Maven的provided比较接近。
  4. testImplementation:这种依赖在测试编译时和运行时可见,类似于Maven的test作用域。
  5. testCompileOnlytestRuntimeOnly:这两种类似于compileOnly和runtimeOnly,但是作用于测试编译时和运行时。
  6. annotationProcessor:用来声明"只在编译期运行的注解处理器依赖",不会进入运行时。Java在编译阶段可以运行一类特殊的程序,叫注解处理器,用来:扫描源码中的注解、生成新代码(.java 文件)或在编译期做校验、直接报错。Gradle用annotationProcessor来告诉编译器:这些依赖,是给"编译器"用的,不是给程序运行用的。

通过简短精悍的依赖配置和多种多样的作用与选择,Gradle可以为我们提供比Maven更加优秀的依赖管理功能。

4. Groovy 语法

Groovy是一种基于JVM的动态语言,语法和Java相似,最终也是要编译.class文件在JVM上运行。Groovy完全兼容Java,在此基础上添加了很多动态类型和灵活特性,比如支持闭包,支持DSL(领域特定语言),是一门非常灵活的动态脚本语言。

要执行Groovy的脚本,必须安装Groovy环境,或者使用Java的ClassLoader来调用。

4.1. 变量、方法、类

1、Groovy中使用def关键字来定义变量,可以不指定变量类型,默认访问修饰符是public

java 复制代码
def a = 1;
def int b = 1;
def c = "hello world!";

2、方法使用返回类型或def关键字定义,方法可以接收任意数量的参数,这些参数可以不申明类型,如果不提供可见性修饰符,则该方法为public。如果指定了方法返回类型,可以不需要def关键字来定义方法。如果不使用return,则方法的返回值为最后一行代码的执行结果。

java 复制代码
def add(int a, int b){
    println a + b
}

int minus(a, b){
    return a - b
}

int minus(a, b){
    a - b
}

省略:

  • 语句后面的分号可以省略。
  • 方法的括号可以省略。
  • 参数类型可以省略。
  • return可以省略。

3、Groovy类非常类似Java类。

java 复制代码
def p = new Person()
    p.increaseAge 5
    println p.age

class Person{
    String name
    integer age = 10
    def increaseAge(Integer years){
        this.age += years
    }
}

区别:

  • 默认类的修饰符为public。
  • 没有可见性修饰符的字段会自动生成对应的setter和getter方法。
  • 类不需要与它的源文件有相同的名称,但还是建议采用相同的名称。

4.2. for、switch

1、Groovy支持Java的for(int i=0; i<N; i++)形式的循环语句,另外还支持for in loop形式,支持遍历范围、列表、Map、数组和字符串等多种类型。

java 复制代码
//遍历范围
def x = 0;
for(i in 0..3){
    x += i
}
assert x == 6
//遍历列表
def x = 0
for(i in [0,1,2,3]){
    x += i
}
assert x == 6
//遍历Map中的值
def map = ['a':1, 'b':2, 'c':3]
x = 0
for(v in map.values()){
    x += v
}
assert x == 6

2、Groovy中的switch语句不仅兼容Java代码,还可以处理更多的case表达式。case表达式可以是字符串、列表、范围、Integer等等。

java 复制代码
def x = 16
def result = ""

switch(x){
    case "ok":
        result = "found ok"
    case [1, 2, 4, 'list']:
        result = "list"
        break
    case 10..19:
        result = "range"
        break
    case Integer:
        result = "integer"
        break
    default:
        result = "default"
}
assert result == "range"

4.3. 数据类型

Groovy中的数据类型主要有以下几种:

  • Java中的基本数据类型。
  • Groovy中的容器类。
  • 闭包

4.3.1. 字符串

在Groovy中有两种字符串类型,普通字符串String(java.lang.String)和插值字符串GString(groovy.lang.GString)。

在Groovy中单引号字符串和双引号字符串都可以定义一个字符串常量,只不过单引号字符串不支持插值。

双引号字符串可以支持插值,插值指的是替换字符串中的占位符,占位符表达式是${}或者以$为前缀。

三引号字符串了可以保留文本的换行和缩进符,不支持插值。

java 复制代码
def name = '我是chian人'
println "hello ${name}"
println "hello $name"

def str = '''中国人和美国人
    一起吃火锅
    好不好?'''

String是不可变的,GString是可变的,GString和String即使有相同的字面量,它们的hashCode也可能不同,因此应该避免使用GString作为Map的Key。

java 复制代码
//当双引号字符串中包含插值表达式时,字符串类型为GString。下面断言为true
assert "one: ${1}".hashCode() != "one: 1".hashCode

4.3.2. List

Groovy在Java集合的基础上进行了增强和简化,Groovy的List对应Java中的List接口,默认的实现类为Java中的ArrayList。可以使用as操作符显式指定List的实现类为java.util.LinkedList。使用[]获取List中具有正索引或负索引的元素。

java 复制代码
def number = [1, 2, 3]
assert number instanceof List
def linkedList = [1, 2, 3] as LinkedList
assert linkedList instanceof java.util.LinkedList

def number = [1, 2, 3, 4]
assert number[1] == 2
//-1表示列表末尾的第一个元素
assert number[-1] == 4
//在列表末尾追加一个元素5
number << 5
assert number[4] == 5
assert number[-1] ==5

4.3.3. Map

Groovy中创建Map同样使用[],需要同时指定键和值,默认的实现为java.util.LinkedHashMap。

java 复制代码
def name = [one: 'aaa', tow: "bbb", three: "ccc"]
assert name['one'] == 'aaa'
assert name.two == 'bbb'

Map中的键关联问题:

java 复制代码
def key = 'name'
//使用字符串key作为键
def person = [key: '张三']
assert person.containKey('key')
//使用变量key作为键
person = [(key): '李四']
assert person.contailsKey('name')

4.4. 闭包

Groovy中的闭包是一个开放的、匿名的、可以接受参数和返回值的代码块。

定义闭包

java 复制代码
{ [closureParameters -> ] statements }

闭包分为两个部分,分别是参数列表部分[closureParameters -> ]和语句部分statements

参数列表部分是可选的,如果闭包只有一个参数,则参数名是可选的,Groovy会隐式指定it作为参数名。

java 复制代码
{ println it} //使用隐式参数it的闭包

当需要指定参数列表时,需要->将参数列表和闭包体分离。

java 复制代码
{ it -> println it}
 
{ String a, String b ->
    println "${a} is ${b}"
}

闭包是groovy.lang.Cloush类的一个实例。这使得闭包可以赋值给变量或字段。

java 复制代码
//将闭包赋值给一个变量
def println = {it -> println it}
assert println instanceof Closure

//将闭包赋值给Closuer类型变量
Closuer do = { println 'do!'}

调用闭包

java 复制代码
def code = {123}
assert code() == 123 //闭包当做方法调用
assert code.call() == 123 //显式调用call方法
def isOddNumber = { int i -> i%2 != 0}
assert isOddNumber(3) == true //调用带参数的闭包

闭包作为方法参数

java 复制代码
//无参数闭包
def test1(Closure closure){
    println "test1 start..."
    closuer(); //调用闭包
    println "test1 end..."
}

test1{prinln "hahaha"}

//有参数闭包
def test2(Closuer closure){
    int a = 100;
    int b = 200;
    println "test2 starte"
    closure(a,b);//调用闭包
    println "test2 end"
}

test2 { x,y ->{
    println("x:" + x)
    println("y:" + y)
  }
}

5. 构建过程

任何由Gradle构建的项目都遵循这个过程,分为三个阶段:

  • Initialization:初始化阶段。按顺序执行init.gradle -> settings.gradle脚本,生成Gradle、Setting、Project对象。
  • Configuration:编译阶段,也叫配置阶段。按顺序执行root目录下的build.gradle -> 子项目build.gradle脚本,生成Task执行流程图。
  • Execution:执行阶段。按照Task执行图顺序运行每一个Task,完成一个个步骤,生成最终的APK文件或JAR包文件。

整个构建过程,官方在一些节点都设置有hook钩子函数,以便我们在构建过程中添加自己的逻辑。影响构建过程。hook钩子函数可以理解为监听函数,另外也可以设置Linsener监听函数。

5.1. Task

Task是Gradle执行的基本单元。Gradle构建项目是先把所有.gradle脚本都执行一遍,编译生成对应的Gradle、Setting、Project对象。然后根据配置,生成一张Task组成的有向无环图

最终Gradle会按照图上一个个Task的关联按顺序挨个执行。每个Task都完成一个特定功能,所有Task都执行一遍,则整个项目的构建任务就完成了。

创建Task

java 复制代码
task hello{
    println "hello world"
}
task (hello2){
    println "hello world2"
}
task ('hello3'){
    println "hello world3"
}
task ("hello4"){
    println "hello world4"
}

Task的Action

Task内部的Action不是唯一的,而是一个集合,可以往里面添加各种 Action,看下面Task中的声明:

java 复制代码
//在Action队列头部添加action
Task.doFirst(Action<? supper Task> action);
Task.doFirst(Closure action);
//在Action队列头部添加action
Task.doLast(Action<? supper Task> action);
Task.doLast(Closure action);

//删除所有的action
Task deleteAllAction();

doFirst和doLast接受的都是闭包。

  • doFirst添加的action最先执行。
  • task自身的action在中间执行,无法在task外部添加,需要在自定义task时写。
  • doLast添加的action最后执行。
java 复制代码
task speak{
    println("This is AA! -- 配置阶段")
    doFirst{
        println("This is doFirst -- inner -- 执行阶段")
    }
    doLast{
        println("This is doLast -- inner -- 执行阶段")
    }
}

speak.doFirst{
    println("This is doFirst -- outer -- 执行阶段")
}

speak.doLast{
    println("This is doLast -- outer -- 执行阶段")
}

执行任务:

java 复制代码
gradle speak

执行结果:

java 复制代码
> Configure project :
This is AA! -- 配置阶段

> Task :speak
This is doFirst -- outer -- 执行阶段
This is doFirst -- inner -- 执行阶段
This is doLast -- inner -- ִ执行阶段
This is doLast -- outer -- ִ执行阶段

Task依赖

Task依赖是指可以在Task之间指定执行顺序,重点理解dependsOn。

  • A.dependsOn B --> 执行A的时候先执行B
  • A.mustRunAfter B --> 同时执行A和B,需要先执行B再执行A。若执行关系不成立则报错。
  • A.shouldRunAfter B --> 同mustRunAfter,区别:执行关系不成立但是不报错。
java 复制代码
task s1{
    doLast{
            println("This is s1")
    }
}
task s2{
    doLast{
            println("This is s2")
    }
}
s1.dependOn s2

或者

java 复制代码
task s1{
    doLast{
            println("This is s1")
    }
}
task s2{
    dependOn s1
    doLast{
            println("This is s2")
    }
}

自定义Task

Gradle中的Task都继承自org.gradle.api.DefaultTask,自定义Task也需要继承这个类,自己写方法,然后加上@TaskAction注解,表示这个方法是Task中的action。可以加多个,按倒序执行。

自定义任务:

java 复制代码
class MyTask extends DefaultTask{
    @TaskAction
    def ss1(){
        println("This is MyTask action1")
    }
    @TaskAction
    def ss2(){
        println("This is MyTask action2")
    }
}

tasks.register('speak', MyTask) {
    println("This is AA")
    doFirst {
        println("This is doFirst")
    }
    doLast {
        println("This is doLast")
    }
}

执行结果:

java 复制代码
PS D:\Project\gradle-demo> gradle speak
This is AA

> Task :speak
This is doFirst
This is MyTask action2
This is MyTask action1
This is doLast

系统自带Task

Gradle提供了很多task给我们使用,比如copy、delete等。

设置默认Task

意思是脚本中我们不调用该Task,设置的默认Task也会执行。

java 复制代码
defaultTasks 'aaa', 'bbb'

tasks.register('aaa') {
    doLast {
        println("Default aaa task")
    }
}
tasks.register('bbb') {
    doLast {
        println("Default bbb task")
    }
}
tasks.register('other') {
    doLast {
        println("not a default task")
    }
}

执行结果

java 复制代码
PS D:\Project\gradle-demo> gradle -q
Default aaa task
Default bbb task
PS D:\Project\gradle-demo> 

5.2. Plugin

什么是插件

插件只是一组任务,几乎所有的任务,如编译任务,设置域对象,设置源文件等都由插件处理。

Gradle本身只是提供了基本的核心功能,其他特性比如编译Java源码等能力都需要通过插件实现。

应用插件

两种方式:

java 复制代码
plugins {
    id 'java'
}

或者

java 复制代码
apply plugin: 'java'

插件都有自己的短名称,上述两种写法都使用了短名称去应用JavaPlugin。我们还可以用完全的名称:

java 复制代码
apply plugin: org.gradle.api.plugins.JavaPlugin

由于Gradle的默认导入,还可以这样写:

java 复制代码
apply plugin: JavaPlugin

插件的应用是幂等的,如果一个插件已经被应用,再次应用不会有任何效果。

一个插件是任何实现了Plugin接口的类,Gradle提供了核心插件作为其发行包的一部分,所以核心插件可以像上述方式一样简单应用,但是对于第三方插件,需要进行配置使插件在构建路径中可用。

核心插件:docs.gradle.org/current/use...

第三方插件:plugins.gradle.org/

插件都做了什么

把插件应用到项目中可以用来扩展项目功能。包括:

  • 将任务添加到项目(如编译、测试)。
  • 使用有用的默认设置对已添加的任务进行预配置。
  • 向项目中添加依赖配置。
  • 通过扩展对现有类型添加新的属性和方法。

Java插件

Gradle中的Java插件,提供了诸如编译、测试、打包等功能。

Java插件为工程定义了许多默认值。如Java源文件位置。如果遵循这些默认规则,那么无需在脚本文件中书写太多代码。当然,Gradle也允许自定义项目中一些规则。

Java插件向项目中添加了大量任务,如下所示:

对于每个添加到该项目中的源集。Java插件将添加以下编译任务。

Java插件还增加了大量的任务构成该项目的生命周期:

Java项目常用的任务

Java项目(使用了Java插件)执行gradle build后,会默认执行下列任务:

java 复制代码
gradle build
:compileJava
:processResources
:classes
:jar
:assemble
:cpmilerTestJava
:processTestResources
:testClasses
:test
:check
:build
BUILD SUCCESSFUL

Total time: 1.12 secs

clean:删除build目录以及所有构建完成的文件。

assemble:编译并打包jar文件,但不会执行单元测试。

test:编译并执行单元测试。

check:编译并测试代码。

相关推荐
Java猿_2 小时前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端
小王师傅663 小时前
【轻松入门SpringBoot】actuator健康检查(上)
java·spring boot·后端
码事漫谈3 小时前
C++高并发编程核心技能解析
后端
码事漫谈3 小时前
C++与浏览器交织-从Chrome插件到WebAssembly,开启性能之门
后端
源代码•宸4 小时前
goframe框架签到系统项目(BITFIELD 命令详解、Redis Key 设计、goframe 框架教程、安装MySQL)
开发语言·数据库·经验分享·redis·后端·mysql·golang
⑩-4 小时前
SpringCloud-Nacos 配置中心实战
后端·spring·spring cloud
java1234_小锋5 小时前
[免费]SpringBoot+Vue勤工助学管理系统【论文+源码+SQL脚本】
spring boot·后端·mybatis·勤工助学
踏浪无痕6 小时前
从 Guava ListenableFuture 学习生产级并发调用实践
后端·面试·架构