构建

本节介绍如何构建 Spring Shell 应用。

Starter 依赖

  1. Spring Shell Starter 依赖

名称 描述

spring-shell-starter

基础 Spring Shell 模块

spring-shell-starter-jansi

使用 JLine jansi 提供者

spring-shell-starter-jni

使用 JLine jni 提供者

spring-shell-starter-jna

使用 JLine jna 提供者

spring-shell-starter-ffm

使用 JLine ffm 提供者 (需要 JDK22+)

spring-shell-starter-test

Spring Shell 测试支持

终端提供者

与程序运行的底层终端进行交互历来是一个相对复杂的过程,尽管表面看起来一切都只是文本交互。

还记得那些旧式手动打字机或点阵打印机吗?字符在光标所在位置打印,如果需要在不同位置打印,光标就需要移动。简而言之,这就是当前终端模拟器的工作方式。

为了更好地访问和理解现有的终端模拟器环境,JLine 可以通过自己的共享库使用原生代码。JLine 检测哪些提供者存在,然后选择使用其中一个。传统上有三种提供者:`jansi`、`jni` 和 `jna`,它们都应提供相同的功能。

我们的 starter 依赖可以用于专门选择其中的一些 JLine 提供者。

FFM

随着 `JDK22` 的发布,*Foreign Function and Memory API* 从预览版中正式推出,它旨在取代 `JNI`,提供更好、更安全的原生 API。

从 `3.4.x` 版本开始,我们增加了对使用 `JLine` 的 `ffm` 终端提供者编译 Spring Shell 应用的支持。这显然意味着应用需要在 `JDK22+` 环境下运行。新的 JDK 中间版本每 6 个月发布一次,长期支持 (LTS) 版本每 2 年发布一次。在 Spring Shell 能与 Spring Framework 对齐的现有 LTS 版本发布之前,我们将使用最新的 JDK 版本。显然,如果您选择使用 `ffm`,这意味着您可能需要在不方便的时候升级您的 JDK。我们还受限于 JLine 本身用于编译其 `ffm` 部分的 JDK 版本。

FFM 本身在使用某些部分时会导致 JVM 打印警告。这些警告对于终端应用来说显然很烦人,因为它可能会干扰并造成一些混乱。在未来的 JDK 版本中,这些警告也将添加到旧的 JNI 模块中,并且在某个时候,这些警告将变为硬错误。用户将被要求手动启用这些原生“不安全”部分。

在命令行中,为此设置的 JVM 选项是

--enable-native-access=ALL-UNNAMED

如果您有一个 Jar 文件,您可以在其 `META-INF/MANIFEST.MF` 中包含此设置。

Enable-Native-Access: ALL-UNNAMED

可以在构建过程中添加,例如在使用 Gradle 时

tasks.named("bootJar") {
    manifest {
        attributes 'Enable-Native-Access': 'ALL-UNNAMED'
    }
}
至于在 JDK 中启用原生部分,JLine 一直积极主动,已经对此进行了检查,如果原生访问未启用,它将抛出错误。

原生支持

将 *Spring Shell* 应用编译为 *GraalVM* 二进制文件的支持主要来自 *Spring Framework* 和 *Spring Boot*,其中此功能称为 *AOT*。AOT (Ahead of Time) 意味着应用上下文在编译时就已准备好,以便进行 *GraalVM* 生成。

在 Spring Framework 和 Spring Boot 的 *AOT* 功能基础上,*Spring Shell* 拥有自己的 *GraalVM* 配置,提供关于二进制文件中应包含哪些内容的提示。通常问题来自尚不包含 *GraalVM* 相关配置或配置不完整的第三方库。

需要使用 *GraalVM Reachability Metadata Repository*,它为第三方库提供了一些缺失的提示。您还需要安装 *GraalVM* 并将 `JAVA_HOME` 指向它。

对于 *Gradle*,添加 graalvm 的原生插件并配置元数据仓库。

plugins {
	id 'org.graalvm.buildtools.native' version '0.9.16'
}

graalvmNative {
	metadataRepository {
        enabled = true
	}
}

使用 ./gradlew nativeCompile 运行 Gradle 构建时,您应该在 build/native/nativeCompile 目录下获得二进制文件。

对于 Maven,使用 spring-boot-starter-parent 作为父项目,您将获得一个 native profile,可用于编译。您需要配置元数据仓库

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <configuration>
                    <metadataRepository>
                        <enabled>true</enabled>
                    </metadataRepository>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
如果您依赖 spring-boot-starter-parent,它会管理 native-maven-plugin 的版本,并保持最新。

使用 ./mvnw native:compile -Pnative 运行 Maven 构建时,您应该在 target 目录下获得二进制文件。

如果一切顺利,可以直接运行此二进制文件,而无需通过 JVM 执行 Boot 应用的 Jar 文件。