GraalVM 原生镜像简介

GraalVM 原生映像提供了一种部署和运行 Java 应用程序的新方法。 与 Java 虚拟机相比,本机映像可以以更小的内存占用和更快的启动时间运行。spring-doc.cadn.net.cn

它们非常适合使用容器映像部署的应用程序,在与“功能即服务”(FaaS) 平台结合使用时尤其有趣。spring-doc.cadn.net.cn

与为 JVM 编写的传统应用程序不同,GraalVM Native Image 应用程序需要提前处理才能创建可执行文件。 这种预先处理涉及从应用程序代码的主入口点静态分析应用程序代码。spring-doc.cadn.net.cn

GraalVM Native Image 是一个完整的、特定于平台的可执行文件。 您无需提供 Java 虚拟机即可运行本机映像。spring-doc.cadn.net.cn

如果您只想开始并试用 GraalVM,可以跳转到开发您的第一个 GraalVM 原生应用程序部分,稍后再返回此部分。

与 JVM 部署的主要区别

GraalVM Native Images 是提前生成的,这意味着原生应用和基于 JVM 的应用之间存在一些关键差异。 主要区别在于:spring-doc.cadn.net.cn

除了这些差异之外, Spring 还使用了一个称为 Spring Ahead-of-Time processing 的过程,这施加了进一步的限制。 请务必至少阅读下一部分的开头部分以了解这些内容。spring-doc.cadn.net.cn

GraalVM 参考文档的本机映像兼容性指南部分提供了有关 GraalVM 限制的更多详细信息。

了解 Spring 预先处理

典型的 Spring Boot 应用程序是相当动态的,配置是在运行时执行的。 事实上, Spring Boot 自动配置的概念在很大程度上取决于对运行时的状态做出反应,以便正确配置。spring-doc.cadn.net.cn

尽管可以告诉 GraalVM 应用程序的这些动态方面,但这样做会抵消静态分析的大部分好处。 因此,当使用 Spring Boot 创建本机映像时,会假定一个封闭世界,并且应用程序的动态方面受到限制。spring-doc.cadn.net.cn

除了 GraalVM 本身创建的限制外,封闭世界假设还意味着以下限制:spring-doc.cadn.net.cn

当这些限制到位时,Spring 可以在构建时执行提前处理,并生成 GraalVM 可以使用的其他资产。 Spring AOT 处理的应用程序通常会生成:spring-doc.cadn.net.cn

如果生成的提示不够,您还可以提供自己的提示。spring-doc.cadn.net.cn

源代码生成

Spring 应用程序由 Spring Bean 组成。 在内部, Spring Framework 使用两个不同的概念来管理 bean。 有 bean 实例,它们是已经创建的实际实例,可以注入到其他 bean 中。 还有一些 bean 定义,用于定义 bean 的属性以及如何创建其实例。spring-doc.cadn.net.cn

如果我们采用典型的@Configuration类:spring-doc.cadn.net.cn

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

	@Bean
	public MyBean myBean() {
		return new MyBean();
	}

}

Bean 定义是通过解析@Configuration类并找到@Bean方法。 在上面的示例中,我们定义了一个BeanDefinition对于名为myBean. 我们还将创建一个BeanDefinition对于MyConfiguration类本身。spring-doc.cadn.net.cn

myBean实例是必需的,Spring 知道它必须调用myBean()方法并使用结果。 在 JVM 上运行时,@Configuration类解析发生在应用程序启动时,并且@Bean方法使用 Reflection 调用。spring-doc.cadn.net.cn

在创建本机映像时, Spring 以不同的方式运行。 而不是解析@Configuration类并在运行时生成 bean 定义,它在构建时执行。 一旦发现 Bean 定义,它们就会被处理并转换为源代码,供 GraalVM 编译器分析。spring-doc.cadn.net.cn

Spring AOT 过程会将上面的配置类转换为如下所示的代码:spring-doc.cadn.net.cn

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 BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
		return BeanInstanceSupplier.<MyBean>forFactoryMethod(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类,但以 GraalVM 可以理解的直接方式。spring-doc.cadn.net.cn

有一个 bean 定义myConfigurationbean,以及一个用于myBean. 当myBeaninstance 是必需的,则BeanInstanceSupplier被调用。 此供应商将调用myBean()方法上的myConfiguration豆。spring-doc.cadn.net.cn

在 Spring AOT 处理期间,您的应用程序将启动,直到 bean 定义可用为止。 在 AOT 处理阶段不会创建 Bean 实例。

Spring AOT 将为你的所有 bean 定义生成这样的代码。 当需要 bean 后处理时(例如,调用@Autowired方法)。 一ApplicationContextInitializer还将生成该 Spring Boot 来初始化ApplicationContext当 AOT 处理的应用程序实际运行时。spring-doc.cadn.net.cn

尽管 AOT 生成的源代码可能很冗长,但它非常可读,并且在调试应用程序时可能会有所帮助。 生成的源文件可以在target/spring-aot/main/sources使用 Maven 和build/generated/aotSources与 Gradle 一起使用。

提示文件生成

除了生成源文件外,Spring AOT 引擎还将生成 GraalVM 使用的提示文件。 提示文件包含 JSON 数据,描述 GraalVM 应如何通过直接检查代码来处理无法理解的事情。spring-doc.cadn.net.cn

例如,您可能正在私有方法上使用 Spring 注释。 Spring 需要使用反射来调用私有方法,即使在 GraalVM 上也是如此。 当出现此类情况时, Spring 可以编写反射提示,以便 GraalVM 知道,即使私有方法未直接调用,它仍然需要在本机映像中可用。spring-doc.cadn.net.cn

提示文件在META-INF/native-image它们被 GraalVM 自动获取。spring-doc.cadn.net.cn

生成的 Hint 文件可以在target/spring-aot/main/resources使用 Maven 和build/generated/aotResources与 Gradle 一起使用。

代理类生成

Spring 有时需要生成代理类,以使用其他功能来增强您编写的代码。 为此,它使用直接生成字节码的 cglib 库。spring-doc.cadn.net.cn

当应用程序在 JVM 上运行时,代理类是在应用程序运行时动态生成的。 创建原生镜像时,需要在构建时创建这些代理,以便 GraalVM 可以包含它们。spring-doc.cadn.net.cn

与源代码生成不同,生成的字节码在调试应用程序时并不是特别有用。 但是,如果您需要检查.class使用工具(如javap您可以在target/spring-aot/main/classes对于 Maven 和build/generated/aotClasses对于 Gradle。