侧边栏壁纸
博主头像
宇宙尽头的餐馆

永远是深夜有多好。

  • 累计撰写 6 篇文章
  • 累计创建 4 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

芝士雪豹 Vol.01 - Gradle入门

EarthCloud
2025-11-14 / 0 评论 / 0 点赞 / 35 阅读 / 0 字

在之前的Spring Boot项目中,我已经使用Maven进行了基本的依赖导入和项目构建,但现在很多的开源项目已经将构建工具从Maven迁移到了Gradle,所以学习一下也是没有坏处的。

用户手册:Gradle用户手册

Gradle的官方用户手册写的相对来说比较烂,各种核心概念和基础用法十分分散,一些机制也没有解释得很清晰,所以我在这里做一个基本的提纲,看完这篇,你就也可以简单上手Gradle力。

在写这篇文章的时候我主要参考了这个视频,解释了Gradle的一些基本机制和构建脚本的底层实现,主讲人是Gradle开发团队的成员,讲得很好,十分推荐!

概述

Gradle 构建工具是一个快速、可靠且适应性强的开源构建自动化工具,拥有优雅且可扩展的声明式构建语言。它不单单是为了Java设计的,Gradle 支持多种语言,并且兼容主流的IDE。我将使用IDEA创建演示项目进行实操,并说明我在使用时遇到的一些问题和解决方案。Gradle的API经常变来变去的,这也是它被人诟病的一点,不过就入门而言还是够用了。相比于Maven只进行依赖管理,自定义任务得靠插件,Gradle 可以很方便管理依赖,而且自定义任务轻松十分优雅愉快。通过自己编写Gradle脚本,可以实现大量功能,无需自己开发插件了。

安装与部署

和Maven一样,Gradle也是基于JVM运行的程序,在Windows上安装Gradle非常简单:

1. 下载最新的 Gradle 发布版,建议下载bin文件的ZIP包。

2. 在合适的位置进行解压,内容文件夹 gradle-9.0.0即为你的本地Gradle主目录。

3. 配置环境变量。我在这里建议添加GRADLE_HOME到系统环境变量中,这样,当升级到不同版本的 Gradle 时,只需更改 GRADLE_HOME 环境变量。在创建GRADLE_HOME后,只需%GRADLE_HOME%\bin加入到Path下即可。

4. 验证配置。打开控制台(或 Windows 命令提示符)并运行 gradle -v 以运行 Gradle 并显示版本,例如:

Gradle 9.0.0

------------------------------------------------------------

Build time:    2025-07-31 16:35:12 UTC

Revision:      328772c6bae126949610a8beb59cb227ee580241

Kotlin:        2.2.0

Groovy:        4.0.27

Ant:           Apache Ant(TM) version 1.10.15 compiled on August 25 2024

Launcher JVM:  24.0.2 (Oracle Corporation 24.0.2+12-54)

Daemon JVM:    C:\Program Files\Java\jdk-24 (no JDK specified, using current Java home)

OS:            Windows 11 10.0 amd64

使用CLI创建Gradle项目

在你想要的目录下打开终端,执行

gradle init

根据提示进行选择后,便会在该目录下生成Gradle项目。

项目结构

与Maven一样,Gradle也规定了自己的项目结构,这保证了其在多个平台上能够顺利执行,一个 Gradle 项目看起来与以下类似:

project

├── .gradle  

├── build   

├── gradle                          

│   ├── libs.versions.toml              

│   └── wrapper

│       ├── gradle-wrapper.jar

│       └── gradle-wrapper.properties

├── gradlew                         

├── gradlew.bat                         

├── settings.gradle(.kts)           

├── subproject-a

│   ├── build.gradle(.kts)              

│   └── src                         

└── subproject-b

    ├── build.gradle(.kts)              

    └── src                  

上面提供了一个多项目构建的基本结构,subproject代表了该工程下的不同模块,用户编写的软件包在其src目录下。下面,我将借助基础项目结构中的各个目录和文件,为你解释Gradle的基本功能。

Gradle Wrapper

与Maven一样,Gradle可以通过项目内置的Wrapper程序进行构建,而不是本机安装的Gradle,这样做能够确保构建的可靠、受控和标准化执行,因为系统机的Gradle版本可能和项目在编写过程中使用的版本有区别。项目结构中的gradlew是在macOS或Linux执行Gradle Wrapper的脚本,gradlew.bat则是在Windows下的脚本。

在Powershell中,使用

./gradlew.bat build

会先下载项目指定的Gradle版本,再执行构建,下面的输出展示了其运行情况

$ gradlew.bat build

Downloading https://services.gradle.org/distributions/gradle-5.0-all.zip

.....................................................................................

Unzipping C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-all\ac27o8rbd0ic8ih41or9l32mv\gradle-5.0-all.zip to C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-al\ac27o8rbd0ic8ih41or9l32mv

Set executable permissions for: C:\Documents and Settings\Claudia\.gradle\wrapper\dists\gradle-5.0-all\ac27o8rbd0ic8ih41or9l32mv\gradle-5.0\bin\gradle

BUILD SUCCESSFUL in 12s

1 actionable task: 1 executed

目录gradle/wrapper下的gradle-wrapper.jar是它负责为项目下载和安装正确版本的 Gradle(如果尚未安装)的程序,gradle-wrapper.properties则是Wrapper的配置文件。要查看项目的Gradle版本,可以使用:

$ gradlew.bat --version

$ gradlew.bat wrapper --gradle-version 7.2

注意:千万不要手动编辑或更改这些文件

Gradle Daemon

Gradle 运行在 Java 虚拟机 (JVM) 上,并使用多个支持库,这些库的初始化时间不可忽略。启动可能很慢。Gradle Daemon(Gradle守护进程)解决了这个问题。它是一个长期运行的后台进程,主要通过以下方式减少构建所需的时间:

  • 在构建之间缓存项目信息

  • 在后台运行,因此每个 Gradle 构建不必等待 JVM 启动

  • 受益于 JVM 中的持续运行时优化

  • 监视文件系统,以便在运行构建之前准确计算需要重新构建的内容

在使用Maven进行构建时,系统会先创建一个JVM用于运行Maven,并在构建完成后销毁它,但就像上文所说,初始化库的时间可能会很长,造成构建十分缓慢。

为了解决这个问题,Gradle会先创建一个持续运行的Daemon进程负责解析依赖项、执行构建脚本、创建和运行任务。而在客户端执行构建命令后,Gradle会生成一个轻量的Client JVM,它只负责向Daemon传递构建参数,并在完成构建后销毁,这样,Gradle构建的时间开销可以大大减少。

Gradle DSL

DSL (Domain-Specific Language,领域特定语言)是一个与GPL(General Programming Language,通用编程语言)相对应的概念,它是一种为特定应用领域(而非通用目的)而设计的计算机语言,比如关系型数据库管理系统所使用的SQL就是一种DSL,专门用于数据库操作。

Gradle采用了Groovy或者Kotlin作为其DSL,本文主要介绍基于Groovy DSL的Gradle构建脚本编写。Groovy是运行在JVM上的动态语言,它提供了许多语法糖和特性,使得它非常适合作为内部DSL的宿主语言。在这里介绍Groovy的几个关键机制和语法糖,这是Gradle利用它设计出简洁易懂DSL的关键。

闭包

Gradle最重要的一个机制是**闭包**,它是 Groovy 中可执行的代码块,可以被传递、赋值给变量,并作为参数传递。这是 Gradle DSL 配置块的基石,使{}便可以创建一个闭包字面量,如下例所示:

// 定义一个简单的闭包

def hello = { println "Hello, World!" }

hello()  // 输出:Hello, World!

同样的,闭包内还能指定参数,定义方式和lambda表达式基本一致:

def sum = { a, b -> a + b }

println sum(3, 4)  // 输出:7

闭包可以作为参数传递给其他方法:

// 定义一个方法,接收一个闭包作为参数

def repeat(int times, Closure closure) {

    times.times { closure(it) }  // 调用闭包times次,并传递当前索引

}

// 调用repeat方法,传递一个闭包

repeat(3) { index ->

    println "Iteration $index"

}

输出:

Iteration 0

Iteration 1

Iteration 2

如果仅仅是一个可以作为变量定义的lambda表达式,闭包机制将不会如此重要,闭包可以使用外部变量,如下:

def name = "Alice"

def greet = { println "Hello, $name" }

greet()  // 输出:Hello, Alice

// 即使变量在闭包定义之后改变,闭包也会捕获最新的值

name = "Bob"

greet()  // 输出:Hello, Bob

这也引出了闭包的另一个重要机制——委托。

委托

委托机制 是 Groovy 闭包的一个独特而强大的特性。它允许一个闭包将其内部的方法调用和属性访问转发给另一个指定的对象(称为委托对象),而不是在闭包自身或默认的上下文中查找。

简单来说,就是当你在闭包中写代码时,Groovy 可以"假装"这些代码是在另一个对象的环境中执行的。

调用闭包的delegate属性可以设置其委托的对象,默认为owner,即定义闭包的对象。 resolveStrategy可以设置委托策略,默认为Closure.OWNER_FIRST,表示先owner中寻找,DSL在设计时一般将其设置为Closure.DELEGATE_FIRST,先从指定的委托对象中寻找该方法。

下面,我们看一个Gradle中借助委托特性的例子。dependencies方法指示了项目所需的依赖,我们这么使用它:

dependencies ({

    implementation('com.google.guava:guava:31.1-jre')

    testImplementation('junit:junit:4.13.2')

})

这之间发生了什么捏:

首先dependencies方法接收一个闭包作为参数,并设置闭包的委托DependencyHandler实例:

// 伪代码,展示原理

void dependencies(Closure configurationClosure) {

    // 创建 DependencyHandler 实例

    DependencyHandler handler = new DependencyHandler()

    

    // 设置闭包的委托对象

    configurationClosure.delegate = handler

    configurationClosure.resolveStrategy = Closure.DELEGATE_FIRST

    

    // 执行闭包

    configurationClosure.call()

}
// 伪代码

class DependencyHandler {

    void implementation(String dependency) {

        // 添加依赖的实现逻辑

    }

    

    void testImplementation(String dependency) {

        // 添加测试依赖的实现逻辑

    }

}
  • 当执行 dependencies() 时,闭包内的 implementation 和 testImplementation 被调用

  • 由于设置了 DELEGATE_FIRST 策略,Groovy 在 DependencyHandler 实例中查找这些方法

  • 找到后执行相应的方法,完成依赖配置

语法糖

明确了这些机制,Gradle还提供了大量语法糖让DSL可读性更强:

a. 省略括号

对于至少一个参数的方法,可以省略方法调用的括号。

// 标准 Groovy/Java 语法

System.out.println("Hello World");

// Groovy 语法糖 - 省略括号

println "Hello World"

b. 自动getter/setter

在 Groovy 中,直接访问一个对象的属性(如 obj.property)实际上会被转换为调用相应的 getter/setter 方法obj.getProperty()obj.setProperty(value))。

c. 闭包参数的特殊写法

如果闭包是方法的最后一个参数,那么闭包字面量可以写在括号外面。

示例

通过这些,我们便可以理解相关DSL的实现:

android {

    compileSdkVersion 30

    

    defaultConfig {

        applicationId "com.example.myapp"

        minSdkVersion 21

        targetSdkVersion 30

    }

    

    buildTypes {

        release {

            minifyEnabled true

            proguardFiles getDefaultProguardFile('proguard-android.txt')

        }

    }

}

这里有多层嵌套的委托:

  • android 闭包的委托是 AppExtension

  • defaultConfig 闭包的委托是 DefaultConfig

  • buildTypes 闭包的委托是 BuildTypeContainer

  • release 闭包的委托是 BuildType

每一层都通过委托机制将配置转发给相应的对象。

还有以下引入插件的DSL

plugins {

    id 'java'

    id 'org.springframework.boot' version '3.5.7'

    id 'io.spring.dependency-management' version '1.1.7'

}

这里我们注意id('org.springframework.boot') 又返回一个 PluginDependencySpec 对象,在这个对象上,我们借助链式编程调用version方法设置其版本,所以实际上,这段代码应写成如下形式:

// 创建配置闭包

Closure pluginsConfiguration = {

    // 这些方法调用会在 PluginDependenciesSpec 委托对象上执行

    this.getDelegate().id('java')

    this.getDelegate().id('org.springframework.boot').version('3.5.7')

    this.getDelegate().id('io.spring.dependency-management').version('1.1.7')

}

// 设置委托和解析策略

pluginsConfiguration.delegate = getPluginDependenciesSpec()

pluginsConfiguration.resolveStrategy = Closure.DELEGATE_FIRST

// 调用 plugins 方法,传入配置闭包

plugins(pluginsConfiguration)

理解了这些,我们就能编写配置和构建脚本了。

Gradle 构建生命周期

阶段 1. 初始化

  • 检测 settings.gradle(.kts) 文件。

  • 创建一个 Settings实例。

  • 评估设置文件以确定哪些项目(和包含的构建)构成了构建。

  • 为每个项目创建一个 Project实例。

阶段 2. 配置

  • 评估参与构建的每个项目的构建脚本 build.gradle(.kts)

  • 为请求的任务创建任务图。

阶段 3. 执行

  • 调度并执行选定的任务。

  • 任务之间的依赖关系决定执行顺序。

  • 任务可以并行执行。

Gradle 设置文件

存在于项目根目录下settings.gradle(.kts) 是每个 Gradle 项目的**入口点**设置文件的主要目的是定义项目结构,通常是将子项目添加到您的构建中,它在初始化阶段执行。因此,在

  • 单项目构建中,设置文件是可选的

  • 多项目构建中,设置文件是强制性的,并声明所有子项目。

我们来看一个例子:

rootProject.name = 'root-project'   

include('sub-project-a')            

include('sub-project-b')

include('sub-project-c')

通过设置rootProject的name属性,来定义项目名称,include方法来添加子项目。

在 Gradle 构建生命周期的早期,初始化阶段会在你的项目根目录中找到设置文件,当找到设置文件 settings.gradle(.kts) 时,Gradle 会实例化一个 Settings对象,Settings 对象的用途之一是允许你声明构建中要包含的所有项目,在 Groovy DSL 中Settings 对象文档可以在这里找到。

1. 定义插件的位置

设置文件可以使用 pluginManagement 块管理构建的插件版本和存储库。它提供了一种方法来定义项目应使用哪些插件以及应从哪些存储库解析它们。

pluginManagement {  

    repositories {

        gradlePluginPortal()

    }

}

2. 应用设置插件

设置文件可以选择性地应用插件,这些插件是配置项目设置所必需的。在下面的示例中,这些插件通常是 [Develocity 插件](https://plugins.gradle.org/plugin/com.gradle.develocity)和 [Toolchain Resolver 插件](https://plugins.gradle.org/plugin/org.gradle.toolchains.foojay-resolver-convention)。

在设置文件中应用的插件仅影响 Settings 对象。

plugins {   

    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"

}

3. 定义根项目名称

设置文件使用 rootProject.name 属性](https://docs.gradle.org.cn/current/javadoc/org/gradle/api/initialization/ProjectDescriptor.html)定义项目名称。

rootProject.name = 'simple-project'     

每个构建只有一个根项目。

4. 定义依赖解析策略

设置文件可以选择性地定义规则和配置,用于跨项目进行依赖解析。它提供了一种集中管理和自定义依赖解析的方法。

dependencyResolutionManagement {    

    repositories {

        mavenCentral()

    }

}

你还可以在此部分中包含版本目录。

5. 向构建添加子项目

设置文件通过使用 include语句添加所有子项目来定义项目结构。

include("sub-project-a")    

include("sub-project-b")

include("sub-project-c")

Gradle 构建脚本

前文提到,Gradle在初始化阶段读settings.gradle为每个子项目创Project实例,然后,Gradle 会查找相应的构建脚本文件,该文件在配置阶段中使用。

实例

让我们看一个例子并将其分解:

plugins {  

    id 'java'  

    id 'org.springframework.boot' version '3.5.7'  

    id 'io.spring.dependency-management' version '1.1.7'  

}  

  

group = 'cloud.earth'  

version = '0.0.1-SNAPSHOT'  

description = 'web-api'  

  

java {  

    toolchain {  

        languageVersion = JavaLanguageVersion.of(24)  

    }}  

  

repositories {  

    mavenCentral()  

}  

  

dependencies {  

    implementation 'org.springframework.boot:spring-boot-starter-web'  

    testImplementation 'org.springframework.boot:spring-boot-starter-test'  

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'  

}  

  

tasks.named('test') {  

    useJUnitPlatform()  

}

tasks.register('hello') {

    println 'Hello, Gradle!'

}

1. 将插件应用于构建

插件用于扩展 Gradle。它们还用于模块化和重用项目配置。

可以使用 PluginDependenciesSpec 插件脚本块应用插件。

首选插件块

plugins {  

    id 'java'  

    id 'org.springframework.boot' version '3.5.7'  

    id 'io.spring.dependency-management' version '1.1.7'  

}  

在示例中,应用了 Gradle 中包含的 Java 插件和SpringBoot相关插件,将我们的项目描述为 Java 应用程序。

2. 定义项目的坐标

group = 'cloud.earth'  

version = '0.0.1-SNAPSHOT'  

description = 'web-api'  

访问Project实例的属性定义项目坐标。

3. 定义Java工具链版本

java {  

    toolchain {  

        languageVersion = JavaLanguageVersion.of(24)  

    }}  

为Java指定版本为24。

4. 定义可以找到依赖项的位置

一个项目通常需要许多依赖项才能完成其工作。依赖项包括插件、库或 Gradle 必须下载才能成功构建的组件。

构建脚本让 Gradle 知道在哪里查找依赖项的二进制文件。可以提供多个位置

repositories {  

    mavenCentral()

}

在示例中,SpringBoot起步依赖和JUnit单元测试依赖将从Maven Central Repository下载。

5. 添加依赖项

一个项目通常需要许多依赖项才能完成其工作。这些依赖项通常是预编译类的库,它们被导入到项目的源代码中。

依赖项通过配置进行管理,并从仓库中检索。

使用 Project.getDependencies() 方法返回的 DependencyHandler 来管理依赖项。使用 Project.getRepositories() 方法返回的 RepositoryHandler 来管理仓库。

dependencies {  

    implementation 'org.springframework.boot:spring-boot-starter-web'  

    testImplementation 'org.springframework.boot:spring-boot-starter-test'  

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'  

}  

不同的方法名标识了其不同的作用域。

6. 注册和配置任务

任务执行一些基本的工作,例如编译类、运行单元测试或打包 WAR 文件。

虽然任务通常在插件中定义,但您可能需要在构建脚本中注册或配置任务。

注册任务会将任务添加到您的项目中。

可以使用 TaskContainer.register(java.lang.String)方法在项目中注册任务

tasks.register('hello') {

    println 'Hello, Gradle!'

}

您可能已经见过 TaskContainer.create(java.lang.String)方法的使用,应避免使用该方法

register() 优于 create(),它支持任务配置规避。

可以使用 TaskCollection.named(java.lang.String) 方法定位任务以对其进行配置

tasks.named('test') { 

    useJUnitPlatform()

}

0

评论区