在多个项目上工作可能需要与多个不同版本的 Java 语言进行交互。即使在单个项目内部,由于向后兼容性要求,代码库的不同部分可能也固定在特定的语言级别。这意味着必须在构建项目的每台机器上安装和管理相同工具(即工具链)的不同版本。

一个 Java 工具链是一套用于构建和运行 Java 项目的工具集合,通常由环境通过本地 JRE 或 JDK 安装提供。编译 Task 可能使用 javac 作为其编译器,测试和执行 Task 可能使用 java 命令,而 javadoc 将用于生成文档。

默认情况下,Gradle 使用相同的 Java 工具链来运行 Gradle 本身并构建 JVM 项目。然而,这并非总是理想的。在不同的开发人员机器和 CI 服务器上使用不同的 Java 版本构建项目可能导致意外问题。此外,您可能希望使用不支持运行 Gradle 的 Java 版本来构建项目。

为了提高构建的可重现性并使构建要求更清晰,Gradle 允许在项目和 Task 级别配置工具链。您还可以使用 Daemon JVM 标准来控制用于运行 Gradle 本身的 JVM。

项目工具链

Gradle 提供了多种方法来配置用于编译和运行项目的 Java 版本。

五种主要机制是

这些设置并非互斥,高级用户可能需要在特定场景下结合使用它们。

1. Java 工具链

要为项目配置工具链,请在 java 扩展块中声明所需的 Java 语言版本

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

java 块非常灵活,支持额外的配置选项。您可以在使用 Java 工具链中了解更多信息。

2. --release 标志

对于严格的交叉编译,建议使用 --release 标志而不是 sourceCompatibilitytargetCompatibility

tasks.withType<JavaCompile>().configureEach {
    options.release = 8
}
tasks.withType(JavaCompile).configureEach {
    options.release = 8
}

此标志可以防止意外使用指定版本中不可用的较新 API。但是,它并不控制使用哪个 JDK——只控制编译器如何处理源代码。

如果同时需要特定的 JDK 和严格的交叉编译,此方法可以与工具链结合使用。

3. Source 和 Target 兼容性

设置 sourceCompatibilitytargetCompatibility 会告知 Java 编译器生成与特定 Java 版本兼容的字节码,但不会强制 Gradle 本身使用哪个 JDK 运行

java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}
java {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

不能保证使用正确的 JDK,并且在 API 已向后移植到旧版 Java 时可能会导致问题。

仅当您需要向后兼容性但无法使用工具链时,才应使用此方法。

4. 环境变量 (JAVA_HOME)

您可以通过设置 JAVA_HOME 环境变量来影响 Gradle 使用哪个 JDK

export JAVA_HOME=/path/to/java17

这为系统上的所有基于 Java 的工具(包括 Gradle 和 Maven)设置了一个默认 JDK。

这不会覆盖 Gradle 的工具链支持或其他项目特定配置。

这种方法对于不使用工具链并期望环境中某个特定 JDK 处于活动状态的遗留项目很有用。

然而,由于 JAVA_HOME 是全局生效的,它不能用于为不同的项目指定不同的 JDK 版本。使用 工具链更可靠,工具链允许在项目级别设置 Java 版本。

5. IDE 设置

大多数现代 IDE 允许您配置在处理项目时用于运行 Gradle 的 JVM。此设置会影响 Gradle 本身在 IDE 内部的执行方式,但不会影响代码的编译方式——除非构建未明确指定工具链。

如果您的构建未定义 Java 工具链,Gradle 可能会回退到使用 IDE 设置定义的 Java 版本。这可能导致意外且不可重现的行为,特别是如果不同的团队成员使用不同的 IDE 配置。

您应该更改 IDE 的 Gradle JVM 设置,使其与命令行上使用的 JVM(JAVA_HOME 或系统的默认 Java 安装)对齐——确保跨环境的一致行为(例如,从 IDE 与终端运行测试或 Task 时)。

如果 IDE 在未设置 JVM 或 JVM 与 JAVA_HOME 不匹配时发出警告/错误,您也应该更改 IDE 的 Gradle JVM 设置。

IntelliJ IDEA

配置 Gradle JVM

  1. 打开 Settings (Preferences) > Build, Execution, Deployment > Gradle

  2. Gradle JVM 设置为所需的 JDK。

Eclipse

配置 Gradle JVM

  1. 打开 Preferences > Gradle > Gradle JDK

  2. 选择相应的 JDK。

某些 IDE 也允许您在相同的设置屏幕中配置 Gradle Daemon 的 JVM。请注意不要将其与工具链或项目 JVM 混淆——请确保您选择了正确的 JVM。

组合使用工具链

在某些情况下,您可能希望

  • 使用特定 JDK 版本进行编译(通过 toolchains)。

  • 确保编译的字节码与旧版 Java 兼容(通过 --releasetargetCompatibility)。

例如,要使用 Java 17 进行编译但生成 Java 11 字节码

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

tasks.withType<JavaCompile>().configureEach {
    options.release = 11
}
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

tasks.withType(JavaCompile).configureEach {
    options.release = 11
}

项目工具链设置对比表

方法 确保使用正确 JDK? 自动下载 JDK? 防止意外使用 API?

Java 工具链

✅ 是

✅ 是

❌ 否

--release 标志

❌ 否

❌ 否

✅ 是

Source 和 Target 兼容性

❌ 否

❌ 否

❌ 否

环境变量 (JAVA_HOME)

✅ 是(但仅限于全局)

❌ 否

❌ 否

IDE 设置

✅ 是(在 IDE 内部)

❌ 否

❌ 否

建议

  • 对于大多数用户: 使用 Java 工具链(toolchain.languageVersion)。

  • 为了严格强制兼容性: 使用 --release 标志。

  • 对于高级情况: 结合使用工具链和 --release

  • 除非必要,避免使用 sourceCompatibilitytargetCompatibility

  • 仅当您需要系统范围的默认 JDK 版本时,才使用 JAVA_HOME

  • 如果您希望 Gradle 与 IDE 的 JDK 版本匹配,则使用 IDE 设置

Task 工具链

如果您想调整特定 Task 使用的工具链,可以指定 Task 使用的确切工具。例如,Test Task 暴露了一个 JavaLauncher 属性,用于定义启动测试时使用的 java 可执行文件。

在下面的示例中,我们将所有 Java 编译 Task 配置为使用 Java 8。此外,我们引入了一个新的 Test Task,它将使用 JDK 17 运行我们的单元测试。

list/build.gradle.kts
tasks.withType<JavaCompile>().configureEach {
    javaCompiler = javaToolchains.compilerFor {
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register<Test>("testsOn17") {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
list/build.gradle
tasks.withType(JavaCompile).configureEach {
    javaCompiler = javaToolchains.compilerFor {
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register('testsOn17', Test) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

此外,在 application 子项目中,我们添加了另一个 Java 执行 Task,以便使用 JDK 17 运行我们的应用程序。

application/build.gradle.kts
tasks.register<JavaExec>("runOn17") {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }

    classpath = sourceSets["main"].runtimeClasspath
    mainClass = application.mainClass
}
application/build.gradle
tasks.register('runOn17', JavaExec) {
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(17)
    }

    classpath = sourceSets.main.runtimeClasspath
    mainClass = application.mainClass
}

根据 Task 的不同,JRE 可能就足够了,而对于其他 Task(例如编译),则需要 JDK。默认情况下,如果已安装的 JDK 能够满足要求,Gradle 会优先选择 JDK 而不是 JRE。

可以从 javaToolchains 扩展中获取工具链工具提供者。

有三个工具可用

与依赖 Java 可执行文件或 Java Home 的 Task 集成

任何可以通过 Java 可执行文件路径或 Java Home 位置配置的 Task 都可以从工具链中受益。

虽然您无法直接连接工具链工具,但它们都包含元数据,可以访问它们的完整路径或所属的 Java 安装路径。

例如,您可以按如下方式为 Task 配置 java 可执行文件

build.gradle.kts
val launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.sampleTask {
    javaExecutable = launcher.map { it.executablePath }
}
build.gradle
def launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('sampleTask') {
    javaExecutable = launcher.map { it.executablePath }
}

另一个示例是,您可以按如下方式为 Task 配置 Java Home

build.gradle.kts
val launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.anotherSampleTask {
    javaHome = launcher.map { it.metadata.installationPath }
}
build.gradle
def launcher = javaToolchains.launcherFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('anotherSampleTask') {
    javaHome = launcher.map { it.metadata.installationPath }
}

如果您需要特定工具(例如 Java 编译器)的路径,可以按如下方式获取:

build.gradle.kts
val compiler = javaToolchains.compilerFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.yetAnotherSampleTask {
    javaCompilerExecutable = compiler.map { it.executablePath }
}
build.gradle
def compiler = javaToolchains.compilerFor {
    languageVersion = JavaLanguageVersion.of(11)
}

tasks.named('yetAnotherSampleTask') {
    javaCompilerExecutable = compiler.map { it.executablePath }
}
上面的示例使用了具有 RegularFilePropertyDirectoryProperty 属性的 Task,这允许延迟配置。执行 launcher.get().executablePathlauncher.get().metadata.installationPathcompiler.get().executablePath 可以获取给定工具链的完整路径,但请注意,这可能会急切地实现(并配置)工具链。

使用 Java 工具链

使用 Java 工具链可以使 Gradle 自动下载并管理构建所需的 JDK 版本。它确保在编译和执行时都使用正确的 Java 版本。

您可以通过在 java 扩展块中指定 Java 语言版本来定义项目使用的工具链

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

现在执行构建(例如使用 gradle check)将为您和运行构建的其他人处理以下几件事:

  1. Gradle 将所有编译、测试和 javadoc Task 配置为使用定义的工具链。

  2. Gradle 检测本地安装的工具链

  3. Gradle 选择一个匹配要求的工具链(对于上面的示例,可以是任何 Java 17 工具链)。

  4. 如果找不到匹配的工具链,Gradle 可以根据配置的工具链下载仓库自动下载匹配的工具链。

工具链支持在 Java 插件及其定义的 Task 中可用。

对于 Groovy 插件,支持编译但尚不支持 Groovydoc 生成。对于 Scala 插件,支持编译和 Scaladoc 生成。

按供应商选择工具链

如果您的构建对使用的 JRE/JDK 有特定要求,您可能还需要定义工具链的供应商。JvmVendorSpec 包含 Gradle 可识别的已知 JVM 供应商列表。优点是 Gradle 可以处理不同 JDK 版本中 JVM 编码供应商信息方式的任何不一致之处。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}

如果您想定位的供应商不是已知供应商,您仍然可以将工具链限制为与可用工具链的 java.vendor 系统属性匹配的工具链。

以下片段使用过滤来包含可用工具链的子集。此示例仅包含其 java.vendor 属性包含给定匹配字符串的工具链。匹配是按大小写不敏感的方式进行的。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.matching("customString")
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.matching("customString")
    }
}

选择支持 GraalVM native image 的工具链

如果您的项目需要具有 GraalVM Native Image 能力的工具链,您可以配置规范来请求它:

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
        nativeImageCapable = true
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
        nativeImageCapable = true
    }
}

将该值留空或设置为 false 不会根据 Native Image 能力限制工具链选择。这意味着如果符合其他标准,可以选择支持 Native Image 的 JDK。

按虚拟机实现选择工具链

如果您的项目需要特定的实现,您也可以根据实现进行过滤。目前可供选择的实现有:

VENDOR_SPECIFIC

作为占位符,匹配任何供应商的任何实现(例如 hotspot, zulu 等)

J9

仅匹配使用 OpenJ9/IBM J9 运行时引擎的虚拟机实现。

例如,要使用通过 AdoptOpenJDK 分发的 IBM JVM,您可以按照下面的示例指定过滤器。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.IBM
        implementation = JvmImplementation.J9
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(11)
        vendor = JvmVendorSpec.IBM
        implementation = JvmImplementation.J9
    }
}
Java 主版本、供应商(如果指定)和实现(如果指定)将作为编译和测试执行的输入进行跟踪。

配置工具链规范

Gradle 允许配置影响工具链选择的多个属性,例如语言版本或供应商。尽管这些属性可以独立配置,但配置必须遵循某些规则才能形成一个有效的规范。

在两种情况下,JavaToolchainSpec 被视为有效

  1. 未设置任何属性,即规范为

  2. 设置了 languageVersion,并且可选地设置了任何其他属性。

换句话说,如果指定了供应商或实现,则必须同时指定语言版本。Gradle 区分配置了语言版本的工具链规范和未配置语言版本的工具链规范。在大多数情况下,没有语言版本的规范将被视为选择当前构建所用工具链的规范。

自 Gradle 8.0 起,使用无效JavaToolchainSpec 实例会导致构建错误。

自动检测已安装的工具链

默认情况下,Gradle 会自动检测本地的 JRE/JDK 安装,因此用户无需进行额外配置。以下是 JVM 自动检测支持的常见包管理器、工具和位置列表。

JVM 自动检测知道如何处理:

在检测到的所有 JRE/JDK 安装中,将根据工具链优先级规则选择一个。

无论您是使用工具链自动检测还是配置自定义工具链位置,不存在或没有 bin/java 可执行文件的安装都将被忽略并发出警告,但不会生成错误。

如何禁用自动检测

为了禁用自动检测,您可以使用 org.gradle.java.installations.auto-detect Gradle 属性

  • 要么使用 -Porg.gradle.java.installations.auto-detect=false 启动 gradle

  • 或者将 org.gradle.java.installations.auto-detect=false 放入您的 gradle.properties 文件中。

自动配置

如果 Gradle 找不到满足构建要求的本地可用工具链,它可以自动下载一个(前提是已配置工具链下载仓库;详情请参阅相关章节)。Gradle 将下载的 JDK 安装到Gradle 用户主目录中。

Gradle 仅下载 GA(通用可用)版本的 JDK。不支持下载早期访问版本。

一旦安装在Gradle 用户主目录中,配置的 JDK 就成为自动检测可见的 JDK 之一,并且可以被后续的任何构建使用,就像系统上安装的任何其他 JDK 一样。

由于自动配置只在自动检测未能找到匹配的 JDK 时才启动,因此自动配置只能下载新的 JDK,并且不参与更新任何已安装的 JDK。自动配置的 JDK 即使有更新的次要版本可用,也不会被自动配置再次访问和更新。

工具链下载仓库

通过应用特定的 settings 插件,可以将工具链下载仓库定义添加到构建中。有关编写此类插件的详细信息,请参阅工具链解析器插件页面。

工具链解析器插件的一个例子是基于 foojay Disco APIFoojay Toolchains 插件。它甚至还有一个约定变体,只需应用即可自动处理所有必要的配置:

settings.gradle.kts
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention").version("0.10.0")
}
settings.gradle
plugins {
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
}

对于高级或高度特定的配置,应使用自定义工具链解析器插件。

通常,应用工具链解析器插件时,还需要配置它们提供的工具链下载解析器。让我们用一个例子来说明。考虑构建应用的两个工具链解析器插件:

  • 其中一个是上面提到的 Foojay 插件,它通过提供的 FoojayToolchainResolver 下载工具链。

  • 另一个包含一个名为 MadeUpResolver虚构解析器。

以下示例通过 settings 文件中的 toolchainManagement 块在构建中使用这些工具链解析器:

settings.gradle.kts
toolchainManagement {
    jvm { (1)
        javaRepositories {
            repository("foojay") { (2)
                resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java
            }
            repository("made_up") { (3)
                resolverClass = MadeUpResolver::class.java
                credentials {
                    username = "user"
                    password = "password"
                }
                authentication {
                    create<DigestAuthentication>("digest")
                } (4)
            }
        }
    }
}
settings.gradle
toolchainManagement {
    jvm { (1)
        javaRepositories {
            repository('foojay') { (2)
                resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver
            }
            repository('made_up') { (3)
                resolverClass = MadeUpResolver
                credentials {
                    username = "user"
                    password = "password"
                }
                authentication {
                    digest(BasicAuthentication)
                } (4)
            }
        }
    }
}
1 toolchainManagement 块中,jvm 块包含 Java 工具链的配置。
2 javaRepositories 块定义了命名的 Java 工具链仓库配置。使用 resolverClass 属性将这些配置链接到插件。
3 工具链声明顺序很重要。Gradle 从列表中第一个提供匹配的仓库下载,从列表中的第一个仓库开始。
4 您可以使用与依赖管理相同的认证和授权选项配置工具链仓库。
toolchainManagement 中的 jvm 块仅在应用工具链解析器插件后解析。

查看和调试工具链

Gradle 可以显示所有检测到的工具链列表,包括它们的元数据。

例如,要显示项目的所有工具链,请运行:

gradle -q javaToolchains
gradle -q javaToolchains 的输出
> gradle -q javaToolchains

 + Options
     | Auto-detection:     Enabled
     | Auto-download:      Enabled

 + AdoptOpenJDK 1.8.0_242
     | Location:           /Users/username/myJavaInstalls/8.0.242.hs-adpt/jre
     | Language Version:   8
     | Vendor:             AdoptOpenJDK
     | Architecture:       x86_64
     | Is JDK:             false
     | Detected by:        Gradle property 'org.gradle.java.installations.paths'

 + Microsoft JDK 16.0.2+7
     | Location:           /Users/username/.sdkman/candidates/java/16.0.2.7.1-ms
     | Language Version:   16
     | Vendor:             Microsoft
     | Architecture:       aarch64
     | Is JDK:             true
     | Detected by:        SDKMAN!

 + OpenJDK 15-ea
     | Location:           /Users/user/customJdks/15.ea.21-open
     | Language Version:   15
     | Vendor:             AdoptOpenJDK
     | Architecture:       x86_64
     | Is JDK:             true
     | Detected by:        environment variable 'JDK16'

 + Oracle JDK 1.7.0_80
     | Location:           /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre
     | Language Version:   7
     | Vendor:             Oracle
     | Architecture:       x86_64
     | Is JDK:             false
     | Detected by:        MacOS java_home

这有助于调试构建有哪些可用的工具链,它们是如何被检测到的,以及 Gradle 对这些工具链了解哪些元数据。

禁用自动配置

为了禁用自动配置,您可以使用 org.gradle.java.installations.auto-download Gradle 属性

  • 要么使用 -Porg.gradle.java.installations.auto-download=false 启动 gradle

  • 或者将 org.gradle.java.installations.auto-download=false 放入 gradle.properties 文件中。

禁用自动配置后,请确保构建文件中指定的 JRE/JDK 版本已在本地安装。然后,停止 Gradle daemon,以便在下次构建时可以重新初始化。您可以使用 ./gradlew --stop 命令停止 daemon 进程。

移除自动配置的工具链

当需要移除自动配置的工具链时,请移除位于Gradle 用户主目录/jdks 目录下的相关工具链。

Gradle Daemon 缓存有关项目的信息,包括工具链路径或版本等配置详情。项目工具链配置的更改可能只有在 Gradle Daemon 重启后才会生效。建议停止 Gradle Daemon,以确保 Gradle 为后续构建更新配置。

自定义工具链位置

如果自动检测本地工具链不足或已禁用,还有其他方法可以让 Gradle 知道已安装的工具链。

如果您的设置已经提供了指向已安装 JVM 的环境变量,您也可以让 Gradle 知道需要考虑哪些环境变量。假设环境变量 JDK8JRE17 指向有效的 Java 安装,以下配置指示 Gradle 解析这些环境变量并在查找匹配工具链时考虑这些安装。

org.gradle.java.installations.fromEnv=JDK8,JRE17

此外,您可以使用 org.gradle.java.installations.paths 属性提供一个逗号分隔的路径列表,指向特定的安装目录。例如,在 gradle.properties 中使用以下内容将让 Gradle 知道在检测工具链时要查看哪些目录。Gradle 会将这些目录视为可能的安装目录,但不会深入到任何嵌套目录中。

org.gradle.java.installations.paths=/custom/path/jdk1.8,/shared/jre11

Gradle 不会将自定义工具链优先于自动检测到的工具链。如果您的构建启用了自动检测,自定义工具链会扩展工具链位置的集合。Gradle 会根据优先级规则选择一个工具链。

工具链安装优先级

Gradle 将对所有匹配构建工具链规范的 JDK/JRE 安装进行排序,并选择第一个。排序依据以下规则:

  1. 当前运行 Gradle 的安装优先于任何其他安装

  2. JDK 安装优先于 JRE 安装

  3. 某些供应商优先于其他供应商;它们的排序(从最高优先级到最低优先级)

    1. ADOPTIUM

    2. ADOPTOPENJDK

    3. AMAZON

    4. APPLE

    5. AZUL

    6. BELLSOFT

    7. GRAAL_VM

    8. HEWLETT_PACKARD

    9. IBM

    10. JETBRAINS

    11. MICROSOFT

    12. ORACLE

    13. SAP

    14. TENCENT

    15. 其他所有

  4. 主要版本号更高优先于更低的主要版本号

  5. 次要版本号更高优先于更低的次要版本号

  6. 安装路径根据其字典顺序具有优先级(用于确定性地决定相同类型、相同供应商和相同版本的安装之间的选择的最后手段标准)

所有这些规则将作为多级排序标准被应用,并遵循所示顺序。让我们用一个例子来说明。一个工具链规范请求使用 Java 17 版本。Gradle 检测到以下匹配的安装:

  • Oracle JRE v17.0.1

  • Oracle JDK v17.0.0

  • Microsoft JDK 17.0.0

  • Microsoft JRE 17.0.1

  • Microsoft JDK 17.0.1

假设 Gradle 运行在一个主要 Java 版本非 17 的环境上。否则,该安装将具有优先权。

当我们应用上述规则对这组安装进行排序时,我们将得到以下排序结果:

  1. Microsoft JDK 17.0.1

  2. Microsoft JDK 17.0.0

  3. Oracle JDK v17.0.0

  4. Microsoft JRE v17.0.1

  5. Oracle JRE v17.0.1

Gradle 偏好 JDK 而非 JRE,因此 JRE 排在最后。Gradle 偏好 Microsoft 供应商而非 Oracle,因此 Microsoft 安装排在最前面。Gradle 偏好较高的版本号,因此 JDK 17.0.1 排在 JDK 17.0.0 之前。

因此 Gradle 按此顺序选择第一个匹配项:Microsoft JDK 17.0.1。

插件作者的工具链

创建使用工具链的插件或任务时,必须提供合理的默认值并允许用户进行覆盖。

对于 JVM 项目,通常可以安全地假定 java 插件已应用于项目。核心 Groovy 和 Scala 插件以及 Kotlin 插件都会自动应用 java 插件。在这种情况下,将通过 java 扩展定义的工具链用作工具属性的默认值是合适的。这样,用户只需在项目级别配置一次工具链。

以下示例展示了如何将默认工具链用作约定,同时允许用户针对每个任务单独配置工具链。

build.gradle.kts
abstract class CustomTaskUsingToolchains : DefaultTask() {

    @get:Nested
    abstract val launcher: Property<JavaLauncher> (1)

    init {
        val toolchain = project.extensions.getByType<JavaPluginExtension>().toolchain (2)
        val defaultLauncher = javaToolchainService.launcherFor(toolchain) (3)
        launcher.convention(defaultLauncher) (4)
    }

    @TaskAction
    fun showConfiguredToolchain() {
        println(launcher.get().executablePath)
        println(launcher.get().metadata.installationPath)
    }

    @get:Inject
    protected abstract val javaToolchainService: JavaToolchainService
}
build.gradle
abstract class CustomTaskUsingToolchains extends DefaultTask {

    @Nested
    abstract Property<JavaLauncher> getLauncher() (1)

    CustomTaskUsingToolchains() {
        def toolchain = project.extensions.getByType(JavaPluginExtension.class).toolchain (2)
        Provider<JavaLauncher> defaultLauncher = getJavaToolchainService().launcherFor(toolchain) (3)
        launcher.convention(defaultLauncher) (4)
    }

    @TaskAction
    def showConfiguredToolchain() {
        println launcher.get().executablePath
        println launcher.get().metadata.installationPath
    }

    @Inject
    protected abstract JavaToolchainService getJavaToolchainService()
}
1 我们在任务上声明一个 JavaLauncher 属性。该属性必须标记为 @Nested 输入,以确保任务能响应工具链的变化。
2 我们从 java 扩展获取工具链规范,用作默认值。
3 使用 JavaToolchainService,我们获取一个匹配工具链的 JavaLauncher 提供者。
4 最后,我们将启动器提供者配置为我们属性的约定。

在应用了 java 插件的项目中,我们可以按如下方式使用该任务:

build.gradle.kts
plugins {
    java
}

java {
    toolchain { (1)
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register<CustomTaskUsingToolchains>("showDefaultToolchain") (2)

tasks.register<CustomTaskUsingToolchains>("showCustomToolchain") {
    launcher = javaToolchains.launcherFor { (3)
        languageVersion = JavaLanguageVersion.of(17)
    }
}
build.gradle
plugins {
    id 'java'
}

java {
    toolchain { (1)
        languageVersion = JavaLanguageVersion.of(8)
    }
}

tasks.register('showDefaultToolchain', CustomTaskUsingToolchains) (2)

tasks.register('showCustomToolchain', CustomTaskUsingToolchains) {
    launcher = javaToolchains.launcherFor { (3)
        languageVersion = JavaLanguageVersion.of(17)
    }
}
1 java 扩展上定义的工具链默认用于解析启动器。
2 没有额外配置的自定义任务将使用默认的 Java 8 工具链。
3 另一个任务使用 javaToolchains 服务通过选择不同的工具链来覆盖启动器的值。

当任务需要访问工具链而未应用 java 插件时,可以直接使用工具链服务。如果向服务提供一个 未配置的 工具链规范,它将始终返回用于运行 Gradle 的工具链的工具提供者。这可以通过在请求工具时传递一个空 lambda 实现:javaToolchainService.launcherFor({})

你可以在 编写任务 文档中找到定义自定义任务的更多详细信息。

工具链的限制

当 Gradle 运行在针对 musl 编译的 JVM 中时,它可能会错误地检测工具链。musl 是 C 标准库的 替代实现。针对 musl 编译的 JVM 有时会覆盖 LD_LIBRARY_PATH 环境变量以控制动态库解析。这可能会影响 Gradle 启动的派生 Java 进程,导致意外的行为。

因此,在包含 musl 库的环境中不建议使用多个 Java 工具链。大多数 Alpine 发行版都属于这种情况 — 请考虑改用其他发行版,例如 Ubuntu。如果你仅使用一个工具链(即运行 Gradle 的 JVM)来构建和运行你的应用程序,你可以安全地忽略此限制。