Gradle核心概念与快速上手

文章目录

安装下载

可以直接从腾讯云下载镜像下载

关于软件版本是一个非常有意思的事情,有兴趣可以看看:理解软件版本标识含义与版本号语义

从gradle的distribution可以看出来,更新非常快,基本上两周一个patch版本,两三个月一个minor版本,一年一个major版本。

  1. gradle-xxx-all.zip:包含源码、文档、示例代码、编译好的可执行文件
  2. gradle-xxx-bin.zip:只包含可执行文件
  3. gradle-xxx-src.zip:如果想要自己编译项目可以使用这个,all中只是源码文件,不是工程文件

一般现在bin版本就可以,不要选all,不然解压都要等很久。

下载好之后直接解压到指定目录就可以:

GRADLE_USER_HOME

首先,我们先来了解一下GRADLE_USER_HOME这个非常重要的目录:

GRADLE_USER_HOME默认在用户目录下的.gradle目录。

Windows:C:\Users\<user_name>\.gradle\

Linux:$HOME/.gradle

我们可以通过GRADLE_USER_HOME环境变量来自定义到指定的目录。

  1. .tmp:临时文件目录,存储构建过程中下载依赖、执行任务时的中间数据:临时jar包、插件解压文件等
  2. build-scan-data:Build Scan缓存数据,记录构建过程的性能、依赖等信息
  3. caches:全局缓存,本地仓库(本地缓存jar包)就在这个目录下,还有插件、构建任务中间缓存等
  4. daemon:Gradle 守护进程的日志和状态文件
  5. jdks:存储 Gradle 通过工具链(Toolchain) 自动下载的JDK
  6. kotlin-profile:Kotlin插件的配置、编译缓存
  7. native:存储平台相关的原生库(如 C/C++ 编译后的.so/.dll文件),适用于 NDK 或 Gradle Native 项目
  8. notifications:存储构建过程中的通知信息,如构建结果、警告的缓存
  9. workers:存储 Gradle 工作线程(Worker API)的元数据、临时文件(用于并行任务执行)
  10. wrapper:存储 Gradle Wrapper 自动下载的 Gradle 发行版(位于wrapper/dists目录下)

重点关注有3个:

  1. caches:Gradle的本地仓库就在caches\modules-2\files-2.1,为什么放在caches下,因为本地仓库其实就相当于本地缓存的jar包
  2. jdks:项目多了,每个项目可能依赖于不同版本的jdk,甚至是不同厂商的jdk,Gradle提供了插件,可以自动下载jdk,下载的jdk就放在这个目录
  3. wrapper:因为Gradle迭代非常快,不同的Major版本很多API不兼容,所以不同项目可能使用不同的Gradle版本,Gradle Wrapper自动下载的Gradle默认就在这个目录下

gradle wrapper

什么是Gradle Wrapper以及为什么需要它

因为gradle的升级非常快,所以不同的版本差异可能会很大,各种项目的版本都需要兼容,所以很难

不像maven,同一个项目(pom.xml)不同的人使用不同版本的maven基本不会有太大问题,如果遇到问题,基本往高版本升级基本不会有太大问题,比较maven现在major版本也才3。

但是gradle的major版本已经到9了,major版本不需要做向下兼容,所以升级major版本的时候一定要小心。

就算是相同的major版本,gradle项目,同一个项目,相同build.gradle配置文件,使用不同的版本很可能会出问题。

例如:

txt 复制代码
Build file 'E:\app\me\gradle\learn-gradle\build.gradle' line: 3

An exception occurred applying plugin request [id: 'org.springframework.boot', version: '4.0.0']
> Failed to apply plugin 'org.springframework.boot'.
   > Spring Boot plugin requires Gradle 8.x (8.14 or later) or 9.x. The current version is Gradle 8.5

所以,通常在gradle项目中会使用一个gradle wrapper的东西,主要就是为项目指定gradle版本,这样当其他人拿到相同的项目去执行构建的时候,就自动去下载对应版本的gradle。

通过gradle wrapper的方式,开发就不用自己去下载管理各种不同的Gradle版本了。

Gradle Wrapper配置(gradle-wrapper.properties)

gradle-wrapper.jar非常小只有几十KB,主要是用来下载gradle的。

如果项目已经使用了gradle-wrapper,那也可以通过配置gradle-wrapper.properties来修改为本地gradle。

gradle-wrapper.properties

properties 复制代码
# 可以直接使用url,默认的就是从官方下载,网络好的,直接用默认的就行
# distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
# 可以配置腾讯云的镜像地址
# distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-9.2.1-bin.zip
# 也可以下载到本地,配置为本地地址,注意是zip文件,不是目录,Gradle会自动拷贝解压这个文件
distributionUrl=file:///D:/ptool/gradle/gradle-9.2.0-bin.zip

networkTimeout=10000
validateDistributionUrl=true

# 下载的解压的Gradle放到什么地方
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
# 下载的Gradle zip文件放在什么地方
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

wrapper工具的主类是GradleWrapperMain,gradle_user_home查找逻辑在GradleUserHomeLookup,具体实现在wrapper子项目中的PathAssembler类。

网速不好就尽量用本地安装包吧,它没有端点续传的功能,每次出现Read timed out之类的错误,都得从头开始下载。

gradlew脚本命令

gradle与gradlew的关系:有点像npm和pnpm,gradlew是gradle的包装,主要是处理了路径问题,使用指定版本的gradle而已。

在项目中,基本我们都会使用wrapper模式,通常我们使用的命令是gradlew,而不是gradle,因为gradle通常是全局统一配置的gradle,gradlew是项目的gradle。

下面的命令是gradlew的基本命令,如果没有使用wrapper,替换为gradle就可以。

基本命令

sh 复制代码
# 查看本地 Gradle 安装版本
gradlew -v
gradlew --version

# 启动守护进程
gradlew --daemon
# 禁用守护进程运行任务
gradlew --no-daemon
# 停止所有守护进程
gradlew --stop

任务

sh 复制代码
# 查看基础任务
gradlew tasks
# 查看所有任务(包括子项目和隐藏任务)
gradlew tasks --all  
gradlew help --task <任务名>
# 查看 build 任务的详情
gradlew help --task build
gradlew help --task assemble
# 编译Java代码
gradlew compileJava
# 仅运行测试和校验:执行代码检查Checkstyle、单元测试、集成测试等校验任务,不打包
gradlew check

# 删除项目的build目录:构建产物、编译缓存等
gradlew clean

依赖*

依赖非常重要,我们需要解决的绝大多数问题都是依赖问题。

所以在看依赖命令之前,我们先来简单的解释一下依赖:

  • implementation:编译和运行都需要
  • api:主要用于库或者框架项目
  • runtimeOnly:编译时需要,如JDBC驱动、日志实现(如logback-classic,编译时只需要日志接口slf4j-api,运行时才需要实现)
  • testImplementation:测试代码编译 + 运行阶段,如JUnit
  • testRuntimeOnly:测试运行时

api我们使用的比较少,但是在开源项目和框架中,我们能大量看到它的身影。

api和implementation最大的不同是,api具有传递性。

什么意思呢?

举个例子,我有一个库项目A:

groovy 复制代码
dependencies {
    api 'com.alibaba:fastjson:2.0.42'

    implementation 'com.google.guava:guava:32.1.3-jre'
}

现在有一个项目B,依赖了A:B->A:

B项目是可以直接使用com.alibaba:fastjson:2.0.42,不需要单独在B项目中引入fastjson。

但是B项目如果要使用com.google.guava:guava:32.1.3-jre,就必须在项目中单独在引入guava。

如果B项目没有在B项目中引入guava,而使用了它,就会出现Class Not Found错误。

什么时候使用api呢?库、框架暴露的接口中包含了依赖的库,例如接口中返回值或者参数使用了JSON,这样依赖的库肯定也需要fastjson,这样就可以使用api。

sh 复制代码
# 查看所有依赖
gradlew dependencies

# 查看指定配置的依赖(推荐,缩小范围)
gradlew dependencies --configuration <配置名>
# 查看指定期依赖(compileClasspath、runtimeClasspath)
gradlew dependencies --configuration compileClasspath
# 查看运行时依赖
gradlew dependencies --configuration runtimeClasspath
# 子项目可以加:子项目名称作为前缀
gradlew :app:dependencies --configuration runtimeClasspath

gradlew dependencyInsight --dependency <依赖名> --configuration <配置名>
# 查看 guava 依赖的详细信息(版本、来源、冲突解决)
# 配置名:compileClasspath、runtimeClasspath、implementation、testCompileClasspath、testRuntimeClasspath、testImplementation
gradlew dependencyInsight --dependency guava --configuration implementation
gradlew :app:dependencyInsight --dependency guava --configuration compileClasspath
# 锁定依赖版本
gradlew dependencies --write-locks

我们可以看到相关内容还是非常详细的。

Gradle默认处理依赖冲突的策略是:选择最高版本,所以,我们看到最终选择了更好版本的guava包。

当然可以指定强制版本:

groovy 复制代码
configurations {
    compileClasspath {
        resolutionStrategy {
            force 'com.google.guava:guava:33.4.6-jre'
        }
    }
}

也可以排除依赖:

groovy 复制代码
dependencies {
    implementation('com.google.guava:guava:33.5.0-jre') {
        exclude group: 'com.google.guava', module: 'listenablefuture'
    }
}

--write-locks是个什么东西呢?因为Gradle支持类似npm的动态版本,不过简化一些:

groovy 复制代码
dependencies {
    // 1. 通配符:匹配 2.15.x 系列的最新版本
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.+'
    // 2. 区间范围:匹配 1.0.0 到 2.0.0 之间的最新版本(左闭右开)
    implementation 'org.apache.commons:commons-lang3:[1.0.0, 2.0.0)'
    // 3. 关键字:匹配最新正式版(不推荐)
    implementation 'com.google.guava:guava:latest.release'
}

有动态版本,自然需要像npm的lock文件,解决不同人拉的库是相同的版本。

为什么需要动态版本呢?

因为我们通常需要自动去依赖patch版本,就是做了bug修复的版本,特别是一些重大漏洞。

但是我们很少随时去关注依赖的包做了哪些更新,复杂的项目依赖成千上万的包,要了解每个依赖包的更新,那啥事都别干了。

怎么办呢?答案就是动态版本。

我们再开发的时候,就可以使用implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.+'这一的版本。

这样,jackson做了patch更新的时候,会修改他们的patch版本号,Gradle就会自动拉取最新的patch版本。

因为,如果jackson遵照规范,新patch版本肯定是API兼容的,所以,我们也不用担心会出现不兼容的情况。

这样,我们不用去随时关系jackson做了什么bug修复、漏洞修复,因为我们使用的是最新的patch版本。

关于软件版本可以参考:理解软件版本标识含义与版本号语义

测试

sh 复制代码
# 运行所有测试
gradlew test

# 运行单个测试类
gradlew test --tests vip.oschool.MyTestClass

# 运行单个测试方法
gradlew test --tests vip.oschool.MyTestClass.myTestMethod

# 通配符匹配
gradlew test --tests "vip.oschool.*"  # 匹配包下所有测试
gradlew test --tests "*MyTestClass*"  # 匹配类名包含的测试

构建

sh 复制代码
# 构建项目
gradlew build
# 先清理,再完整构建
gradlew clean build
# -x 表示排除指定任务
gradlew build -x test
# 刷新依赖
gradlew build --refresh-dependencies
gradlew build --parallel
# 手动更新锁定版本,有动态版本的时候
gradlew build --update-locks
# 离线模式,通常用于测试
gradlew build --offline

注意一下--refresh-dependencies参数,这个是去强制刷新snapshot版本和动态版本依赖,不是去删除所有依赖重新下载。

通常我们不会使用snapshot版本,很多公司也不会使用动态版本,所以--refresh-dependencies很多时候没有必要。

安装发布包

sh 复制代码
# 将产物安装到本地 Maven 仓库
gradlew install
# 发布到仓库
gradlew publish

scan

构建扫描,全方位分析构建过程,并生成报告,会上传到Gradle云端,公司项目慎用。

sh 复制代码
# 普通构建并生成扫描报告
gradlew build --scan

# 对指定子项目执行任务并生成扫描
gradlew :app:build --scan

# 依赖分析并生成扫描
gradlew :app:dependencies --scan

工具链配置

工具链主要用来指定JDK版本的,Gradle脚本中可以为不同的项目、插件、甚至任务指定不同的JDK版本。

不同版本怎么管理切换呢?

答案是:toolchain

groovy 复制代码
// 自定义使用版本
java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
        vendor = JvmVendorSpec.AMAZON
    }
}

常用的vendor:ADOPTIUM、AZUL、AMAZON、MICROSOFT

Gradle会自动探测JDK,顺序如下:

  1. 系统属性
  2. 环境变量 JAVA_HOME
  3. GRADLE_USER_HOME/.gradle/jdks/
  4. /usr/lib/jvm/
  5. C:\Program Files\Java\ (Windows)
sh 复制代码
# 查看可用工具链
gradlew -q javaToolchains

javaToolchains

Adoptium-jdk-华中科技

Adoptium-jdk-清华

openjdk

injdk

自动下载JDK插件

注意Gradle插件分为两类:

  1. Settings 插件:仅作用于构建初始化阶段,必须在settings.gradle(.kts)中应用
  2. Project 插件:作用于具体项目,在build.gradle(.kts)中应用

例如我们自动下载JDK的插件,可以通过settings.gradle配置:

groovy 复制代码
pluginManagement {
    plugins {
        id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
    }
}

// 根据java.toolchain配置自动下载JDK
plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention'
}

网速不行,自动下载基本就没不要,但是可以通过Gradle属性org.gradle.java.installations.paths指定本地位置。

实测,网速还是非常快啊,比很多时候自己下都快:

下载完有4个文件:

Gradle各种配置文件

Gradle属性配置文件(gradle.properties)

和SpringBoot一样可以指定application.properties一样,gradle也可以配置gradle.properties

有很多地方可以放,优先级从高到低;

  1. 命令行参数
  2. 项目根目录的gradle.properties(如果有子项目,子项目自己的优先级更高)
  3. GRADLE_USER_HOME目录(可-Dgradle.user.home修改)下gradle.properties(默认位置:用户目录/.gradle/gradle.properties)
  4. 环境变量(比较麻烦,通常使用比较少)

属性文件还是比较有用,例如我们自己的本地的JDK,怎么告诉Gradle呢,就可以通过org.gradle.java.installations.paths属性配置

又比如说,我们因为仓库需要使用代理,那么我们就可以通过属性文件配置。

因为这些不是项目配置,是我们自己比较个性化的配置,所以就可以单独建一个gradle.properties文件,放在GRADLE_USER_HOME目录下。

这样就不会影响项目的配置,并且我们本地的所有Gradle的项目都可以共享这些配置。

properties 复制代码
org.gradle.java.installations.paths=D:/Env/JDK/openjdk16,D:/Env/JDK/openjdk17,D:/Env/JDK/openjdk19,D:/Env/JDK/openjdk21

systemProp.http.proxyHost=198.185.120.100
systemProp.http.proxyPort=7000
systemProp.http.proxyUser=tim
systemProp.http.proxyPassword=123456

systemProp.https.proxyHost=198.185.120.100
systemProp.https.proxyPort=7000
systemProp.https.proxyUser=tim
systemProp.https.proxyPassword=123456

# -Dorg.gradle.jvmargs=-Xmx2048m
# org.gradle.jvmargs=-Xmx512m

命令行的配置优先级最高,通常是某一次运行的特殊配置,比如:

sh 复制代码
gradle -Dorg.gradle.java.installations.paths=D:/Env/JDK/openjdk17 -Pgradle.user.home=F:/custom/home

我们再build.gradle脚本中,可以通过类似下面的方式直接获取属性值:

groovy 复制代码
println("test.gradle.c=" + project.getProperty("test.gradle.c"))
System.properties.each { key, value ->
    if (key.startsWith('http.proxy') || key.startsWith('https.proxy')) {
        println "  $key = $value"
    }
}

Gradle构建环境

Gradle所有初始化脚本、构建脚本及其优先级

除了扩展名为.properties的属性文件,Gradle还有扩展名是.gradle构建脚本,最常见的就是项目根目录下的build.gradle文件。

除了项目下的build.gradle文件,Gradle有一些初始化脚本。

  1. 在命令行指定文件,例如:gradle --init-script F:/tmp/init.gradle -q taskName
  2. GRADLE_USER_HOME目录下的init.gradle文件
  3. GRADLE_USER_HOME目录下的init.d目录下的*.init.gradle(所有以.init.gradle结尾的文件)
  4. GRADLE_HOME目录下的init.d目录下的*.init.gradle(所有以.init.gradle结尾的文件)

注意:

  1. 如果使用的是kotlin,指的是包含.kts后缀的文件,例如:build.gradle.kts
  2. GRADLE_USER_HOME目录下文件名必须是:init.gradle,其子目录init.d下的只需要以.init.gradle结尾即可
  3. 通常我们使用GRADLE_USER_HOME,而不是GRADLE_HOME

初始化脚本,可以用来放我们自己的一些工具脚本。

例如前面我们放在properties属性文件中的org.gradle.java.installations.paths,也可以通过init.gradle初始化脚本来设置:

groovy 复制代码
System.setProperty('org.gradle.java.installations.paths', 
    'E:/language/java/openjdk17,E:/language/java/openjdk21,E:/language/java/openjdk22,E:/language/java/openjdk23,E:/language/java/openjdk24,E:/language/java/openjdk25')

代理也可以:

groovy 复制代码
System.properties.putAll([
    'http.proxyHost': 'proxy.company.com',
    'http.proxyPort': '8080',
    'https.proxyHost': 'proxy.company.com',
    'https.proxyPort': '8080',
])

统一的仓库管理,这样避免在每个项目中再单独去配置仓库地址。

groovy 复制代码
allprojects {
    buildscript {
        repositories {
            maven { url 'https://maven.aliyun.com/repository/public' }
            maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
            gradlePluginPortal()
        }
    }
    
    repositories {
        maven { url 'https://maven.aliyun.com/repository/public' }
        mavenCentral()
    }
}

建议:本地jDK、仓库地址、代理地址、插件仓库地址、jvm都统一配置,不然,每次导入项目的时候,都要去配置一遍。

最关键的问题是,想IDEA这些集成环境导入项目就自动开始下载了,如果没有提前配置好,就使用了默认的配置了。

Gradle初始化脚本

gradle在IDEA配置

IDEA对Gradle的支持比较好,我们来简单说一下几个重要的问题:

创建项目选Gradle就可以:

也可以通过命令行初始化,按照步骤走就可以:

sh 复制代码
gradle init

理解build.gradle配置原理

build.gradle配置文件其实是一个脚本文件,使用的是Groovy或者Kotlin语言。

只是Gradle相当于提供了很多工具,例如:build.gradle你就可以直接使用preoject等对象。

要想清楚Gradle配置的原理,只需要理解Groovy的闭包。

下面就是一个简单的示例,可以直接放在build.gradle文件中,刷新就能执行。

groovy 复制代码
class ServerConfig {
    String host
    int port
    List<String> plugins = []

    def host(String host) { this.host = host }
    def port(int port) { this.port = port }
    def plugin(String plugin) { plugins << plugin }
}

class ConfigBuilder {
    ServerConfig serverConfig = new ServerConfig()

    def server(@DelegatesTo(ServerConfig) Closure closure) {
        closure.delegate = serverConfig
        // 执行闭包中的方法,闭包中没有的时候,先去查找闭包的delegate,这里就是serverConfig
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure()
        // Groovy默认最后一行是作为返回值,所以server方法返回了ServerConfig
        serverConfig
    }
}

// 创建ConfigBuilder对象,并调用server方法
// server后面的{}就是闭包,就是一个代码块,被编译为Closure传给server方法
// server中closure()就相当于调用{}代码块
// 因为代码块中没有 host方法,就调用代理serverConfig的host方法
def config = new ConfigBuilder().server {
    // 调用host方法,闭包本身没有,先去查找闭包的delegate,就是ServerConfig的host方法
    host "localhost"
    // 同理,这里相当于调用ServerConfig的port方法
    port 8080
    plugin "security"
    plugin "monitoring"
}

println config.host
println config.port
println config.plugins

不熟悉Groovy没有关系,如果你会Java,Groovy就非常简单,Groovy就是Java的增强、脚本化。

有兴趣可以看一下下面2篇文章你就能够快速上手Groovy:

快速上手Groovy

深入理解Groovy

资源

gradle官网

gradle services

gradle distributions

war
task

gradle Sync

下载镜像

清华大学镜像

华中科技大学镜像

腾讯云下载镜像

gradle下载

gradle github

相关推荐
trayvontang16 小时前
理解Gradle各种仓库
gradle·gradle仓库
SinFeeLoo_星凤楼2 天前
Android Studio 中gradle.properties 中的中文注释显示乱码,如何解决?
android·ide·gradle·android studio·.properties
I'm Jie3 天前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
一线大码4 天前
Gradle 基础篇之基础知识的介绍和使用
后端·gradle
雨声不在6 天前
gradle编译missing_rules报错处理
android·gradle·agp8
明川8 天前
Android Gradle - ASM + AsmClassVisitorFactory插桩使用
android·前端·gradle
Coffeeee10 天前
Android15适配之世上本无坑,targetSdkVersion升到35后全是坑
android·前端·gradle
明川12 天前
Android Gradle学习 - Gradle插件开发与发布指南
android·前端·gradle
原来是好奇心14 天前
深入Spring Boot源码(一):环境搭建与初探项目架构
java·gradle·源码·springboot