不用框架做网站,东莞正规的免费网站优化,ppt模板怎么做,建设一个网站的好处本文为官方文档直译版本。原文链接 GraalVM 原生镜像支持中文文档 引言GraalVM 原生镜像介绍与 JVM 部署的主要区别了解 Spring Ahead-of-Time 处理源代码生成生成提示文件生成代理类 开发您的第一个 GraalVM 原生应用程序应用样本使用构建包构建原生映像系统要求使用 Maven使用… 本文为官方文档直译版本。原文链接 GraalVM 原生镜像支持中文文档 引言GraalVM 原生镜像介绍与 JVM 部署的主要区别了解 Spring Ahead-of-Time 处理源代码生成生成提示文件生成代理类 开发您的第一个 GraalVM 原生应用程序应用样本使用构建包构建原生映像系统要求使用 Maven使用 Gradle运行示例 使用原生构建工具构建原生映像先决条件Linux 和 macOSWindows 使用 Maven使用 Gradle运行示例 测试 GraalVM 原生镜像使用 JVM 测试超前处理使用原生构建工具进行测试使用 Maven使用 Gradle 高级原生图像主题嵌套配置属性转换 Spring Boot 可执行 Jar使用构建包使用 GraalVM 原生镜像 使用跟踪代理直接启动应用程序 自定义提示测试自定义提示 已知限制 引言
GraalVM 原生映像是独立的可执行文件可通过提前处理已编译的 Java 应用程序生成。与 JVM 相比本地映像通常内存占用更小启动速度更快。
GraalVM 原生镜像介绍
GraalVM 本地镜像提供了一种部署和运行 Java 应用程序的新方法。与 Java 虚拟机相比本地映像的内存占用更小启动速度更快。 原生镜像非常适合使用容器镜像部署的应用程序与 “功能即服务”FaaS平台相结合时尤其有趣。 与为 JVM 编写的传统应用程序不同GraalVM 原生镜像应用程序需要提前处理才能创建可执行文件。这种超前处理包括从主入口点对应用程序代码进行静态分析。 GraalVM Native Image 是一个完整的、特定于平台的可执行文件。要运行原生映像您不需要安装 Java 虚拟机。 如果你只是想开始尝试 GraalVM可以跳到 “开发你的第一个 GraalVM 原生应用程序” 部分稍后再返回本部分。 与 JVM 部署的主要区别
GraalVM 原生镜像是提前生成的这意味着原生应用程序和基于 JVM 的应用程序之间存在一些关键差异。主要区别有
应用程序的静态分析在构建时从主入口点开始执行。创建原生映像时无法触及的代码将被删除不会成为可执行文件的一部分。GraalVM 无法直接感知代码中的动态元素因此必须了解反射、资源、序列化和动态代理。应用程序的类路径在构建时是固定的不能更改。没有懒类库加载可执行文件中的所有内容都将在启动时加载到内存中。Java 应用程序的某些方面存在一些限制无法完全支持。
除了这些差异之外Spring 还使用了一种称为 Spring Ahead-of-Time 处理的流程它带来了更多限制。请务必至少阅读下一节的开头部分以了解这些限制。 GraalVM 参考文档中的《原生镜像兼容性指南》部分提供了有关 GraalVM 限制的更多详细信息。 了解 Spring Ahead-of-Time 处理
典型的 Spring Boot 应用程序是相当动态的配置是在运行时执行的。事实上Spring Boot 自动配置的概念在很大程度上依赖于对运行时的状态做出反应以便正确配置。 虽然可以告诉 GraalVM 应用程序的这些动态方面但这样做会使静态分析的大部分优势化为乌有。因此在使用 Spring Boot 创建原生镜像时我们会假设一个封闭的世界并限制应用程序的动态方面。 除了 GraalVM 本身造成的限制外封闭世界假设还意味着以下限制
应用程序中定义的 Bean 不能在运行时更改这意味着 Spring Profile 注解和特定于配置文件的配置有其局限性。不支持在创建 Bean 时改变的属性例如ConditionalOnProperty 和 .enable 属性。
有了这些限制Spring 就有可能在构建时执行超前处理并生成 GraalVM 可以使用的额外资产。经过 Spring AOT 处理的应用程序通常会生成以下内容
Java 源代码字节码用于动态代理等GraalVM JSON 提示文件 资源提示 (resource-config.json)反射提示reflect-config.json序列化提示serialization-config.jsonJava 代理提示proxy-config.jsonJNI 提示jni-config.json
源代码生成
Spring 应用程序由 Spring Beans 组成。在内部Spring Framework 使用两种不同的概念来管理 Bean。一种是 Bean 实例即已创建并可注入其他 Bean 的实际实例。还有 bean 定义用于定义 bean 的属性以及创建实例的方式。 如果我们使用一个典型的 Configuration 类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration(proxyBeanMethods false)
public class MyConfiguration {Beanpublic MyBean myBean() {return new MyBean();}}Bean 定义是通过解析 Configuration 类并找到 Bean 方法创建的。在上面的示例中我们为名为 myBean 的单例 Bean 定义了 BeanDefinition。我们还为 MyConfiguration 类本身创建了一个 BeanDefinition。 当需要使用 myBean 实例时Spring 知道它必须调用 myBean() 方法并使用结果。在 JVM 上运行时Configuration 类的解析会在应用程序启动时进行Bean 方法会使用反射调用。 创建原生映像时Spring 的运行方式有所不同。它不是在运行时解析 Configuration 类和生成 Bean 定义而是在构建时进行解析。一旦发现 bean 定义就会对其进行处理并将其转换为 GraalVM 编译器可以分析的源代码。 Spring AOT 流程会将上面的配置类转换成这样的代码
import org.springframework.beans.factory.aot.BeanInstanceSupplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;/*** Bean definitions for {link MyConfiguration}.*/
public class MyConfiguration__BeanDefinitions {/*** Get the bean definition for myConfiguration.*/public static BeanDefinition getMyConfigurationBeanDefinition() {Class? beanType MyConfiguration.class;RootBeanDefinition beanDefinition new RootBeanDefinition(beanType);beanDefinition.setInstanceSupplier(MyConfiguration::new);return beanDefinition;}/*** Get the bean instance supplier for myBean.*/private static BeanInstanceSupplierMyBean getMyBeanInstanceSupplier() {return BeanInstanceSupplier.MyBeanforFactoryMethod(MyConfiguration.class, myBean).withGenerator((registeredBean) - registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());}/*** Get the bean definition for myBean.*/public static BeanDefinition getMyBeanBeanDefinition() {Class? beanType MyBean.class;RootBeanDefinition beanDefinition new RootBeanDefinition(beanType);beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());return beanDefinition;}}根据 Bean 定义的性质生成的确切代码可能会有所不同。 您可以在上面看到生成的代码会创建与 Configuration 类等价的 Bean 定义但生成的方式是 GraalVM 可以直接理解的。 myConfiguration 类有一个 bean 定义myBean 也有一个。当需要 myBean 实例时会调用 BeanInstanceSupplier。该供应商将调用 myConfiguration Bean 上的 myBean() 方法。 在 Spring AOT 处理过程中应用程序启动到 Bean 定义可用的阶段。在 AOT 处理阶段不会创建 Bean 实例。 Spring AOT 会为您的所有 Bean 定义生成类似的代码。当需要对 bean 进行后处理时例如调用 Autowired 方法它也会生成代码。此外还将生成 ApplicationContextInitializerSpring Boot 将使用它在 AOT 处理过的应用程序实际运行时初始化 ApplicationContext。
生成提示文件
除了生成源文件Spring AOT 引擎还会生成 GraalVM 使用的提示文件。提示文件包含 JSON 数据描述了 GraalVM 应如何处理直接检查代码无法理解的事情。 例如您可能在一个私有方法上使用了 Spring 注释。即使在 GraalVM 上Spring 也需要使用反射才能调用私有方法。出现这种情况时Spring 可以编写一个反射提示这样 GraalVM 就会知道即使没有直接调用私有方法它仍然需要在原生映像中可用。 提示文件会在 META-INF/native-image 下生成并由 GraalVM 自动获取。 使用 Maven 时可在 target/spring-aot/main/resources 中找到生成的提示文件使用 Gradle 时可在 build/generated/aotResources 中找到生成的提示文件。 生成代理类
Spring 有时需要生成代理类以增强代码的附加功能。为此它会使用 cglib 库直接生成字节码。 当应用程序在 JVM 上运行时代理类会随着应用程序的运行而动态生成。在创建原生映像时需要在构建时创建这些代理以便 GraalVM 将其包含在内。 与源代码生成不同生成的字节码在调试程序时并无特别帮助。不过如果需要使用 javap 等工具检查 .class 文件的内容可以在 Maven 的 target/spring-aot/main/classes 和 Gradle 的 build/generated/aotClasses 中找到它们。 开发您的第一个 GraalVM 原生应用程序
现在我们已经对 GraalVM 原生镜像和 Spring Ahead-of-Time引擎的工作原理有了一个很好的了解我们可以看看如何创建一个应用程序。 构建 Spring Boot 本地镜像应用程序主要有两种方法
使用 Spring Boot 对云原生构建包的支持生成包含原生可执行文件的轻量级容器。使用 GraalVM 原生构建工具生成本地可执行文件。 启动新原生 Spring Boot 项目的最简单方法是访问 start.spring.io添加 “GraalVM Native Support” 依赖关系并生成项目。随附的 HELP.md 文件将提供入门提示。 应用样本
我们需要一个可以用来创建原生图像的示例应用程序。就我们的目的而言“getting-started.html” 部分所介绍的简单的 “Hello World” 网络应用程序就足够了。 概括地说我们的主应用程序代码如下
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RestController
SpringBootApplication
public class MyApplication {RequestMapping(/)String home() {return Hello World!;}public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}}该应用程序使用 Spring MVC 和嵌入式 Tomcat两者都经过测试和验证可以与 GraalVM 原生图像一起使用。
使用构建包构建原生映像
Spring Boot 包含直接为 Maven 和 Gradle 原生镜像提供的 buildpack 支持。这意味着您只需键入一条命令就能在本地运行的 Docker 守护进程中快速获取合理的映像。生成的映像不包含 JVM而是静态编译本地映像。这样一来映像的体积就更小了。 镜像使用的生成器是 paketobuildpacks/builder-jammy-tiny:late。它占用空间小减少了攻击面但如果需要你也可以使用 paketobuildpacks/builder-jammy-base:latest 或 paketobuildpacks/builder-jammy-full:latest 在镜像中提供更多工具。 系统要求
应安装 Docker。详情请参阅获取 Docker。如果使用 Linux请将其配置为允许非 root 用户访问。 你可以运行 docker run hello-world不带 sudo来检查 Docker 守护进程是否能如期到达。详情请查阅 Maven 或 Gradle Spring Boot 插件文档。 在 macOS 上建议将分配给 Docker 的内存至少增加到 8GB还可以增加 CPU。更多详情请参阅 Stack Overflow 答案。在 Microsoft Windows 上确保启用 Docker WSL 2 后端以获得更好的性能。
使用 Maven
要使用 Maven 构建本地镜像容器应确保 pom.xml 文件使用了 spring-boot-starter-parent 和 org.graalvm.buildtools:native-maven-plugin。您的 parent 部分应该如下所示
parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.2.1/version
/parent此外您还应在 build plugins 部分中添加此功能
plugingroupIdorg.graalvm.buildtools/groupIdartifactIdnative-maven-plugin/artifactId
/pluginspring-boot-starter-parent 声明了一个本地配置文件该配置文件配置了创建本地映像时需要运行的执行程序。你可以使用命令行上的 -P 标志激活配置文件。 如果不想使用 spring-boot-starter-parent则需要为 Spring Boot 插件中的 process-aot 目标和原生构建工具插件中的 add-reachability-metadata 目标配置执行。 要构建镜像可以在激活原生配置文件的情况下运行 spring-boot:build-image 目标
$ mvn -Pnative spring-boot:build-image使用 Gradle
当应用 GraalVM Native Image 插件时Spring Boot Gradle 插件会自动配置 AOT 任务。您应该检查 Gradle 构建是否包含 org.graalvm.buildtools.native 的插件块。 只要应用了 org.graalvm.buildtools.native 插件bootBuildImage 任务就会生成本地镜像而不是 JVM 镜像。您可以使用
$ gradle bootBuildImage运行示例
运行相应的构建命令后Docker 镜像就会出现。你可以使用 docker run 启动应用程序
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT您将看到类似下面的输出
. ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | _ | _| | _ \/ _ | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / /|_||___//_/_/_/:: Spring Boot :: (v3.2.1)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)启动时间因机器而异但应该比在 JVM 上运行的 Spring Boot 应用程序快得多。 如果打开浏览器访问 localhost:8080应该会看到以下输出
Hello World!要优雅地退出应用程序请按 ctrl-c。
使用原生构建工具构建原生映像
如果想不使用 Docker 直接生成本地可执行文件可以使用 GraalVM 本地构建工具。原生构建工具是 GraalVM 为 Maven 和 Gradle 提供的插件。你可以使用它们来执行各种 GraalVM 任务包括生成原生镜像。
先决条件
要使用原生构建工具构建原生映像您需要在机器上安装 GraalVM 发行版。您可以在 Liberica 本地镜像工具包页面手动下载也可以使用 SDKMAN 等下载管理器。
Linux 和 macOS
要在 macOS 或 Linux 上安装本地映像编译器我们建议使用 SDKMAN! 从 sdkman.io 获取 SDKMAN! 并使用以下命令安装 Liberica GraalVM 发行版
$ sdk install java 22.3.r17-nik
$ sdk use java 22.3.r17-nik通过检查 java -version 的输出确认已配置正确的版本
$ java -version
openjdk version 17.0.5 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.58-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.58-LTS, mixed mode)Windows
在 Windows 上请按照以下说明安装 GraalVM 或 Liberica 原生映像工具包 22.3 版、Visual Studio 编译工具和 Windows SDK。由于 Windows 相关命令行的最大长度限制请确保使用 x64 Native Tools Command Prompt 而不是常规的 Windows 命令行来运行 Maven 或 Gradle 插件。
使用 Maven
与 buildpack 支持一样你需要确保使用 spring-boot-starter-parent 来继承本地配置文件并使用 org.graalvm.buildtools:native-maven-plugin 插件。 原生配置文件激活后你可以调用 native:compile 目标来触发native-image编译
$ mvn -Pnative native:compile原生镜像可执行文件可在target目录下找到。
使用 Gradle
当原生构建工具 Gradle 插件应用于项目时Spring Boot Gradle 插件会自动触发 Spring AOT 引擎。任务依赖关系会自动配置因此只需运行标准的 nativeCompile 任务即可生成原生镜像
$ gradle nativeCompile本地映像可执行文件可在 build/native/nativeCompile 目录中找到。
运行示例
至此应用程序应该可以正常运行了。现在您可以直接运行应用程序来启动它 Maven $ target/myproject Gradle $ build/native/nativeCompile/myproject 您应该会看到类似下面的输出 . ____ _ __ _ _/\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | _ | _| | _ \/ _ | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / /|_||___//_/_/_/:: Spring Boot :: (v3.2.1)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)启动时间因机器而异但应该比在 JVM 上运行的 Spring Boot 应用程序快得多。 如果打开浏览器访问 localhost:8080应该会看到以下输出
Hello World!要优雅地退出应用程序请按 ctrl-c。
测试 GraalVM 原生镜像
在编写原生镜像应用程序时我们建议您尽可能继续使用 JVM 来开发大部分单元测试和集成测试。这将有助于缩短开发人员的构建时间并允许您使用现有的集成开发环境集成。有了 JVM 上广泛的测试覆盖范围您就可以将原生镜像测试重点放在可能存在差异的地方。 对于原生镜像测试您通常需要确保以下方面正常工作
Spring AOT 引擎可以处理您的应用程序并以 AOT 处理模式运行。GraalVM 有足够的提示来确保生成有效的本地映像。
使用 JVM 测试超前处理
Spring Boot 应用程序运行时会尝试检测它是否作为原生镜像运行。如果是作为原生镜像运行它将使用 Spring AOT 引擎在构建时生成的代码初始化应用程序。 如果应用程序是在普通 JVM 上运行那么任何 AOT 生成的代码都将被忽略。 由于原生镜像编译阶段可能需要一段时间才能完成因此在 JVM 上运行应用程序但让它使用 AOT 生成的初始化代码有时会很有用。这样做有助于快速验证 AOT 生成的代码中是否存在错误以及在应用程序最终转换为本地映像时是否有遗漏。 要在 JVM 上运行 Spring Boot 应用程序并让它使用 AOT 生成的代码可以将 spring.aot.enabled 系统属性设置为 true。 例如
$ java -Dspring.aot.enabledtrue -jar myapplication.jar您需要确保测试的 jar 包含 AOT 生成的代码。对于 Maven这意味着您应该使用 -Pnative 进行构建以激活原生配置文件。对于 Gradle您需要确保您的构建包含 org.graalvm.buildtools.native 插件。 如果您的应用程序在启动时将 spring.aot.enabled 属性设置为 true那么您就有更大的把握相信它在转换为原生映像时会正常工作。 您还可以考虑针对运行中的应用程序运行集成测试。例如你可以使用 Spring WebClient 来调用应用程序的 REST 端点。或者可以考虑使用 Selenium 等项目来检查应用程序的 HTML 响应。
使用原生构建工具进行测试
GraalVM 原生构建工具包含在本地镜像中运行测试的功能。当你想深入测试应用程序的内部功能是否能在 GraalVM 原生镜像中正常工作时这将非常有用。 生成包含要运行的测试的原生镜像可能是一项耗时的操作因此大多数开发人员可能更倾向于在本地使用 JVM。不过作为 CI 管道的一部分它们还是非常有用的。例如您可能会选择每天运行一次本地测试。 Spring Framework 支持提前运行测试。所有常用的 Spring 测试功能都适用于原生镜像测试。例如你可以继续使用 SpringBootTest 注解。您还可以使用 Spring Boot 测试片仅测试应用程序的特定部分。 Spring Framework 的原生测试支持以如下方式工作
对测试进行分析以发现所需的任何 ApplicationContext 实例。对每个应用上下文进行超前处理并生成资产。创建原生镜像生成的资产由 GraalVM 处理。本机镜像还包括配置了已发现测试列表的 JUnit TestEngine。启动原生镜像触发引擎引擎将运行每个测试并报告结果。
使用 Maven
要使用 Maven 运行本地测试请确保您的 pom.xml 文件使用了 spring-boot-starter-parent 。您的 parent 部分应如下所示
parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.2.1/version
/parentspring-boot-starter-parent 声明了一个 nativeTest 配置文件用于配置运行原生测试所需的执行程序。可以使用命令行中的 -P 标志激活配置文件。 如果不想使用 spring-boot-starter-parent则需要为 Spring Boot 插件中的 process-test-aot 目标和原生构建工具插件中的test目标配置执行。 要构建镜像并运行测试请使用test目标并激活 nativeTest 配置文件
$ mvn -PnativeTest test使用 Gradle
当应用 GraalVM Native Image 插件时Spring Boot Gradle 插件会自动配置 AOT 测试任务。你应该检查你的 Gradle 构建是否包含 org.graalvm.buildtools.native 插件块。 要使用 Gradle 运行原生测试可以使用 nativeTest 任务
$ gradle nativeTest高级原生图像主题
嵌套配置属性
Spring ahead-of-time 引擎会自动为配置属性创建反射提示。不过不是内部类的嵌套配置属性必须使用 NestedConfigurationProperty 进行注解否则将无法检测到也无法绑定。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;ConfigurationProperties(prefix my.properties)
public class MyProperties {private String name;NestedConfigurationPropertyprivate final Nested nested new Nested();public String getName() {return this.name;}public void setName(String name) {this.name name;}public Nested getNested() {return this.nested;}}其中 Nested 是
public class Nested {private int number;public int getNumber() {return this.number;}public void setNumber(int number) {this.number number;}}上面的示例产生了 my.properties.name 和 my.properties.nested.number 的配置属性。如果嵌套字段上没有 NestedConfigurationProperty 注解my.properties.nested.number 属性就无法在原生镜像中绑定。 使用构造函数绑定时必须用 NestedConfigurationProperty 对字段进行注解
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;ConfigurationProperties(prefix my.properties)
public class MyPropertiesCtor {private final String name;NestedConfigurationPropertyprivate final Nested nested;public MyPropertiesCtor(String name, Nested nested) {this.name name;this.nested nested;}public String getName() {return this.name;}public Nested getNested() {return this.nested;}}使用record时必须用 NestedConfigurationProperty 对参数进行注解
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;ConfigurationProperties(prefix my.properties)
public record MyPropertiesRecord(String name, NestedConfigurationProperty Nested nested) {}使用 Kotlin 时您需要用 NestedConfigurationProperty 来注解数据类的参数
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.NestedConfigurationPropertyConfigurationProperties(prefix my.properties)
data class MyPropertiesKotlin(val name: String,NestedConfigurationProperty val nested: Nested
)请在所有情况下使用公共getter和setter否则属性将无法绑定。 转换 Spring Boot 可执行 Jar
只要 Spring Boot 可执行 jar 包含 AOT 生成的资产就可以将其转换为原生镜像。这样做有很多好处包括
您可以保留常规的 JVM 管道并在 CI/CD 平台上将 JVM 应用程序转换为原生镜像。由于原生镜像不支持交叉编译因此您可以保留一个操作系统中立的部署工件之后再将其转换为不同的操作系统架构。
您可以使用云原生构建包或 GraalVM 随附的原生镜像工具将 Spring Boot 可执行 jar 转换为原生镜像。 您的可执行 jar 必须包含 AOT 生成的资产如生成的类和 JSON 提示文件。 使用构建包
Spring Boot 应用程序通常通过 Maven (mvn spring-boot:build-image) 或 Gradle (gradle bootBuildImage) 集成使用云原生构建包。不过您也可以使用 pack 将 AOT 处理过的 Spring Boot 可执行 jar 变成原生容器映像。 首先确保 Docker 守护进程可用详情请参阅获取 Docker。如果你使用的是 Linux 系统请将其配置为允许非 root 用户使用。 还需要按照 buildpacks.io 上的安装指南安装软件包。 假设目标目录中有一个经过 AOT 处理的 Spring Boot 可执行 jar构建为 myproject-0.0.1-SNAPSHOT.jar请运行
$ pack build --builder paketobuildpacks/builder-jammy-tiny \--path target/myproject-0.0.1-SNAPSHOT.jar \--env BP_NATIVE_IMAGEtrue \my-application:0.0.1-SNAPSHOT您无需在本地安装 GraalVM 即可通过这种方式生成镜像。 打包完成后就可以使用 docker run 启动应用程序了
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT使用 GraalVM 原生镜像
将经过 AOT 处理的 Spring Boot 可执行 jar 变为本地可执行文件的另一种方法是使用 GraalVM 原生镜像工具。为此你需要在机器上安装 GraalVM 发行版。你可以在 Liberica 原生镜像工具包页面手动下载也可以使用 SDKMAN 等下载管理器。 假定目标目录中有一个经过 AOT 处理的 Spring Boot 可执行 jar构建为 myproject-0.0.1-SNAPSHOT.jar请运行
$ rm -rf target/native
$ mkdir -p target/native
$ cd target/native
$ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar
$ native-image -H:Namemyproject META-INF/native-image/argfile -cp .:BOOT-INF/classes:find BOOT-INF/lib | tr \n :
$ mv myproject ../这些命令可在 Linux 或 macOS 机器上使用但您需要将它们调整到 Windows 系统。 META-INF/native-image/argfile 可能不会打包在你的 jar 中。只有在需要覆盖可达性元数据时才会包含它。 native-image -cp 标志不接受通配符。您需要确保列出所有 jar上述命令使用 find 和 tr 来做到这一点。 使用跟踪代理
GraalVM 原生镜像跟踪代理允许您拦截 JVM 上的反射、资源或代理使用情况以便生成相关提示。Spring 会自动生成大部分提示但跟踪代理可用于快速识别缺失的条目。 使用代理为本地映像生成提示时有几种方法
直接启动应用程序并对其进行练习。运行应用程序测试来运行应用程序。
第一个选项对于在 Spring 无法识别库或模式时识别缺失的提示很有意义。 对于可重复设置来说第二个选项听起来更有吸引力但默认情况下生成的提示将包括测试基础架构所需的任何内容。当应用程序真正运行时其中有些提示就没有必要了。为了解决这个问题代理支持访问过滤文件该文件会将某些数据排除在生成的输出之外。
直接启动应用程序
使用以下命令启动附加了原生镜像跟踪代理的应用程序
$ java -Dspring.aot.enabledtrue \-agentlib:native-image-agentconfig-output-dir/path/to/config-dir/ \-jar target/myproject-0.0.1-SNAPSHOT.jar现在您可以执行需要提示的代码路径然后使用 ctrl-c 停止应用程序。 应用程序关闭时原生镜像跟踪代理会将提示文件写入给定的配置输出目录。您既可以手动检查这些文件也可以将它们作为原生映像构建过程的输入。要将它们作为输入文件请将它们复制到 src/main/resources/META-INF/native-image/ 目录中。下次构建原生镜像时GraalVM 将考虑这些文件。 原生镜像跟踪代理还可以设置更多高级选项例如按调用者类别过滤记录的提示等。如需进一步阅读请参阅官方文档。
自定义提示
如果需要为反射、资源、序列化、代理使用等提供自己的提示可以使用 RuntimeHintsRegistrar API。创建一个实现 RuntimeHintsRegistrar 接口的类然后对提供的 RuntimeHints 实例进行适当的调用
import java.lang.reflect.Method;import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;public class MyRuntimeHints implements RuntimeHintsRegistrar {Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {// Register method for reflectionMethod method ReflectionUtils.findMethod(MyClass.class, sayHello, String.class);hints.reflection().registerMethod(method, ExecutableMode.INVOKE);// Register resourceshints.resources().registerPattern(my-resource.txt);// Register serializationhints.serialization().registerType(MySerializableClass.class);// Register proxyhints.proxies().registerJdkProxy(MyInterface.class);}}然后你就可以在任何 Configuration 类例如 SpringBootApplication 注解的应用程序类上使用 ImportRuntimeHints 来激活这些提示。 如果你的类需要绑定主要是在序列化或反序列化 JSON 时需要你可以在任何 Bean 上使用 RegisterReflectionForBinding。大多数提示都是自动推断的例如在接受或从 RestController 方法返回数据时。但是当您直接使用 WebClient、RestClient 或 RestTemplate 时您可能需要使用 RegisterReflectionForBinding。
测试自定义提示
RuntimeHintsPredicates API 可用于测试您的提示。该 API 提供了构建谓词的方法可用于测试 RuntimeHints 实例。 如果您使用 AssertJ您的测试将如下所示
import org.junit.jupiter.api.Test;import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.docs.nativeimage.advanced.customhints.MyRuntimeHints;import static org.assertj.core.api.Assertions.assertThat;class MyRuntimeHintsTests {Testvoid shouldRegisterHints() {RuntimeHints hints new RuntimeHints();new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());assertThat(RuntimeHintsPredicates.resource().forResource(my-resource.txt)).accepts(hints);}}已知限制
GraalVM 原生镜像是一项不断发展的技术并非所有库都提供支持。GraalVM 社区通过为尚未发布自己的项目提供可达性元数据来提供帮助。Spring 本身并不包含对第三方库的提示而是依赖于可达性元数据项目。 如果您在为 Spring Boot 应用程序生成本地镜像时遇到问题请查看 Spring Boot wiki 的 Spring Boot with GraalVM 页面。您还可以向 GitHub 上的 spring-aot-smoke-tests 项目提交问题该项目用于确认常见应用类型是否按预期运行。 如果你发现某个库无法与 GraalVM 协同工作请在可达性元数据项目中提出问题。