Introducing GraalVM Native Images
GraalVM 本机映像提供了一种部署和运行 Java 应用程序的新方法。与 Java 虚拟机相比,本机映像可以在更小的内存占用空间中运行,并且启动速度快得多。 它们非常适合使用容器映像部署的应用程序,尤其适合与 “功能即服务” (FaaS) 平台结合使用时。 与为 JVM 编写的传统应用程序不同,GraalVM 本机映像应用程序需要预先处理才能创建可执行文件。这种预先处理涉及从其主要入口点静态分析应用程序代码。 GraalVM Native Image 是一款针对特定平台的完整可执行文件。您无需部署 Java Virtual Machine 来运行本机映像。
|
如果您只是想开始使用并试验 GraalVM,您可以跳至 “Developing Your First GraalVM Native Application” 部分,之后再返回此部分。 |
Key Differences with JVM Deployments
GraalVM Native Image 是预先生成的,这意味着本机应用程序和基于 JVM 的应用程序之间存在一些关键差异。主要差异如下:
-
在生成
main入口点的构建时对您的应用程序进行静态分析。 -
在创建本机映像时无法访问的代码将被移除,并且不会成为可执行文件的一部分。
-
GraalVM 不会直接识别代码的动态元素,必须了解反射、资源、序列化和动态代理。
-
应用程序类路径在构建时固定,并且无法更改。
-
没有延迟类加载,在可执行文件中部署的所有内容都将在启动时加载到内存中。
-
围绕一些 Java 应用程序的某些方面有一些限制,这些方面并未得到完全支持。
基于这些差异,Spring 使用一个称为 Spring Ahead-of-Time processing 的进程,这会带来进一步的限制。请务必至少阅读下一部分的开头,以了解这些限制。
|
GraalVM 参考文档中的 {url-graal-docs-native-image}/metadata/Compatibility/[本机映像兼容性指南] 部分提供了有关 GraalVM 限制的更多详细信息。 |
Understanding Spring Ahead-of-Time Processing
典型的 Spring Boot 应用程序是相当动态的,配置在运行时执行。事实上,Spring Boot 自动配置的概念在很大程度上依赖于对运行时状态做出反应,以便正确配置各项工作。
尽管可以将这些应用程序的动态方面告知 GraalVM,但这样做会消除静态分析的大部分好处。因此,在使用 Spring Boot 创建本机映像时,会假定一个封闭的世界,而应用程序的动态方面受到限制。
封闭世界假设暗示了,除了 the limitations created by GraalVM itself 之外,以下限制:
-
应用程序中定义的 Bean 在运行时不可更改,这意味着:
-
Spring
@Profile注解和特定于概要文件的配置 have limitations 。 -
如果创建 bean,则不会支持发生更改的属性(例如,
@ConditionalOnProperty和.enable属性)。
-
当这些限制到位时,Spring 可以在构建时执行预先处理,并生成 GraalVM 可以使用的其他资。经过 Spring AOT 处理的应用程序通常会生成:
-
Java source code
-
字节码(用于动态代理等)
-
GraalVM JSON hint files:
-
Resource hints (
resource-config.json) -
Reflection hints (
reflect-config.json) -
Serialization hints (
serialization-config.json) -
Java Proxy Hints (
proxy-config.json) -
JNI Hints (
jni-config.json)
-
Source Code Generation
Spring 应用程序由 Spring Bean 组成。Spring Framework 在内部使用两个不同的概念来管理 bean。有 bean 实例,它们是已经创建的实际实例,并且可以注入到其他 bean 中。还有 bean 定义,用于定义 bean 的属性以及如何创建其实例。
如果我们取一个典型的 @Configuration 类:
bean 定义由解析 @Configuration 类和查找 @Bean 方法而创建。在以上示例中,我们为名为 myBean 的单例 bean 定义一个 BeanDefinition。我们还为 MyConfiguration 类本身创建一个 BeanDefinition。
当 myBean 实例必需时,Spring 知道它必须调用 myBean() 方法并使用结果。在 JVM 上运行时,@Configuration 类解析会在您的应用程序启动时发生,而 @Bean 方法则会利用反射机制调用。
在创建一个本机映像时,Spring 将以不同的方式运作。它不会在运行时解析 @Configuration 类并生成 bean 定义,而是在构建时进行此操作。在 bean 定义被发现后,它们会被处理并转换为可以被 GraalVM 编译器分析的源代码。
Spring AOT 流程将以上配置类转换为类似这样的代码:
|
根据 Bean 定义的性质,生成的 exact 代码可能会有所不同。 |
您可以看到,上面生成是代码为 @Configuration 类创建了等效的 Bean 定义,但以 GraalVM 可以理解的直接方式进行。
存在一个适用于 myConfiguration bean 的 Bean 定义,以及一个适用于 myBean 的 Bean 定义。当需要一个 myBean 实例时,将调用一个 BeanInstanceSupplier。此供应商将在 myConfiguration bean 上调用 myBean() 方法。
|
在 Spring AOT 处理期间,你的应用程序将启动到 Bean 定义可用的地步。Bean 实例不会在 AOT 处理阶段创建。 |
Spring AOT 将为您的所有 Bean 定义生成类似这样的代码。当需要 Bean 后处理时,它还将生成代码(例如,调用 @Autowired 方法)。还将生成一个 ApplicationContextInitializer,Spring Boot 将使用它在 AOT 处理的应用程序实际上运行时初始化 ApplicationContext。
|
虽然 AOT 生成的源代码冗长,但它非常易读,并且在调试应用程序时很有帮助。使用 Maven 时,可以在 |
Hint File Generation
除了生成源文件外,Spring AOT 引擎还将生成 GraalVM 使用的提示文件。提示文件包含 JSON 数据,描述了 GraalVM 如何处理它不能通过直接检查代码理解的事物。
例如,您可能在私有方法中使用 Spring 注解。Spring 将需要使用反射来调用私有方法,即使是在 GraalVM 上也是如此。当出现这种情况时,Spring 可以编写一个反射提示,以便 GraalVM 知道即使没有直接调用私有方法,它仍然需要在本地映像中可用。
提示文件在 META-INF/native-image 下生成,GraalVM 会在那里自动选取它们。
|
使用 Maven 时,可以在 |
Proxy Class Generation
Spring 有时需要生成代理类以使用其他功能来增强您编写的代码。为此,它使用了直接生成字节码的 cglib 库。
当应用程序在 JVM 上运行时,代理类会在应用程序运行时动态生成。在创建一个本地映像时,这些代理需要在构建时创建,以便它们可以被 GraalVM 包含。
|
与源码生成不同,在调试程序时, 生成的字节码并不是特别有帮助。但是,如果你需要使用诸如 |