此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.1.10Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.1.10Spring中文文档

本章介绍了 Spring 的提前 (AOT) 优化。Spring中文文档

有关特定于集成测试的 AOT 支持,请参阅测试的提前支持Spring中文文档

提前优化简介

Spring 对 AOT 优化的支持旨在检查构建时的决策和发现逻辑,这些逻辑通常发生在运行时。 这样做可以构建一个更直接的应用程序启动安排,并专注于一组主要基于类路径和 .ApplicationContextEnvironmentSpring中文文档

尽早应用此类优化意味着以下限制:Spring中文文档

另请参阅“最佳实践”部分。

当这些限制到位时,就可以在构建时执行提前处理并生成额外的资产。 Spring AOT 处理的应用程序通常会生成:Spring中文文档

目前,AOT 专注于允许使用 GraalVM 将 Spring 应用程序部署为本机映像。 我们打算在未来几代人中支持更多基于 JVM 的用例。
另请参阅“最佳实践”部分。
目前,AOT 专注于允许使用 GraalVM 将 Spring 应用程序部署为本机映像。 我们打算在未来几代人中支持更多基于 JVM 的用例。

AOT 引擎概述

用于处理排列的 AOT 引擎的入口点是 。它负责以下步骤,基于表示要优化的应用程序和 GenerationContextApplicationContextApplicationContextAotGeneratorGenericApplicationContextSpring中文文档

  • 刷新 AOT 处理。与传统的刷新相反,此版本仅创建 Bean 定义,而不创建 Bean 实例。ApplicationContextSpring中文文档

  • 调用可用的实现,并对 . 例如,核心实现遍历所有候选 Bean 定义,并生成必要的代码来恢复 .BeanFactoryInitializationAotProcessorGenerationContextBeanFactorySpring中文文档

此过程完成后,将使用应用程序运行所需的生成代码、资源和类进行更新。 该实例还可用于生成相关的 GraalVM 本机映像配置文件。GenerationContextRuntimeHintsSpring中文文档

ApplicationContextAotGenerator#processAheadOfTime返回允许使用 AOT 优化启动上下文的入口点的类名。ApplicationContextInitializerSpring中文文档

以下各节更详细地介绍了这些步骤。Spring中文文档

刷新 AOT 处理

所有实现都支持刷新 AOT 处理。 应用程序上下文是使用任意数量的入口点创建的,通常采用带注释的类的形式。GenericApplicationContext@ConfigurationSpring中文文档

让我们看一个基本的例子:Spring中文文档

	@Configuration(proxyBeanMethods=false)
	@ComponentScan
	@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
	public class MyApplication {
	}

使用常规运行时启动此应用程序涉及许多步骤,包括类路径扫描、配置类解析、Bean 实例化和生命周期回调处理。 AOT 处理的刷新仅应用常规刷新所发生情况的子集。 可以按如下方式触发 AOT 处理:Spring中文文档

		RuntimeHints hints = new RuntimeHints();
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(MyApplication.class);
		context.refreshForAotProcessing(hints);
		// ...
		context.close();

在此模式下,将像往常一样调用 BeanFactoryPostProcessor 实现。 这包括配置类解析、导入选择器、类路径扫描等。 这些步骤确保包含应用程序的相关 Bean 定义。 如果 Bean 定义受条件(例如 )保护,则在此阶段将丢弃这些条件。BeanRegistry@ProfileSpring中文文档

由于此模式实际上不会创建 Bean 实例,因此不会调用实现,但与 AOT 处理相关的特定变体除外。 这些是:BeanPostProcessorSpring中文文档

  • MergedBeanDefinitionPostProcessor实现后处理 Bean 定义以提取其他设置,例如 AND 方法。initdestroySpring中文文档

  • SmartInstantiationAwareBeanPostProcessor如有必要,实现会确定更精确的 Bean 类型。 这样可以确保创建运行时所需的任何代理。Spring中文文档

完成此部分后,将包含应用程序运行所需的 Bean 定义。它不会触发 Bean 实例化,但允许 AOT 引擎检查将在运行时创建的 Bean。BeanFactorySpring中文文档

Bean 工厂初始化 AOT 贡献

想要参与此步骤的组件可以实现 BeanFactoryInitializationAotProcessor 接口。 每个实现都可以根据 Bean 工厂的状态返回一个 AOT 贡献。Spring中文文档

AOT 贡献是贡献生成的代码以重现特定行为的组件。 它还有助于指示对反射、资源加载、序列化或 JDK 代理的需求。RuntimeHintsSpring中文文档

可以使用等于接口的完全限定名称的密钥注册实现。BeanFactoryInitializationAotProcessorMETA-INF/spring/aot.factoriesSpring中文文档

A 也可以直接由 Bean 实现。 在此模式下,Bean 提供的 AOT 贡献等同于它在常规运行时提供的功能。 因此,此类 Bean 会自动从 AOT 优化的上下文中排除。BeanFactoryInitializationAotProcessorSpring中文文档

如果 Bean 实现了接口,则 Bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础结构 Bean 实现,例如具有有限依赖关系且已在 Bean 工厂生命周期早期初始化的 Bean。 如果使用工厂方法注册了这样的 Bean,请确保该方法不必初始化其封闭类。BeanFactoryInitializationAotProcessorBeanFactoryPostProcessor@Beanstatic@ConfigurationSpring中文文档

Bean 注册 AOT 贡献

核心实现负责为每个候选人收集必要的贡献。 它使用专用的 .BeanFactoryInitializationAotProcessorBeanDefinitionBeanRegistrationAotProcessorSpring中文文档

此接口的用法如下:Spring中文文档

  • 由 Bean 实现,以替换其运行时行为。 例如,AutowiredAnnotationBeanPostProcessor 实现此接口以生成注入带有 .注释的成员的代码。BeanPostProcessor@AutowiredSpring中文文档

  • 由注册的类型实现,其密钥等于接口的完全限定名称。 通常在需要针对核心框架的特定功能调整 Bean 定义时使用。META-INF/spring/aot.factoriesSpring中文文档

如果 Bean 实现了接口,则 Bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础结构 Bean 实现,例如具有有限依赖关系且已在 Bean 工厂生命周期早期初始化的 Bean。 如果使用工厂方法注册了这样的 Bean,请确保该方法不必初始化其封闭类。BeanRegistrationAotProcessorBeanFactoryPostProcessor@Beanstatic@ConfigurationSpring中文文档

如果没有处理特定的注册 Bean,那么缺省实现将处理它。 这是默认行为,因为调整为 Bean 定义生成的代码应限制在极端情况下。BeanRegistrationAotProcessorSpring中文文档

以前面的例子为例,我们假设如下:DataSourceConfigurationSpring中文文档

@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {

	@Bean
	public SimpleDataSource dataSource() {
		return new SimpleDataSource();
	}

}

因为这个班级没有任何特殊条件,并且被确定为候选人。 AOT 引擎会将上述配置类转换为类似于以下内容的代码:dataSourceConfigurationdataSourceSpring中文文档

/**
 * Bean definitions for {@link DataSourceConfiguration}
 */
public class DataSourceConfiguration__BeanDefinitions {
	/**
	 * Get the bean definition for 'dataSourceConfiguration'
	 */
	public static BeanDefinition getDataSourceConfigurationBeanDefinition() {
		Class<?> beanType = DataSourceConfiguration.class;
		RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
		beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);
		return beanDefinition;
	}

	/**
	 * Get the bean instance supplier for 'dataSource'.
	 */
	private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() {
		return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource")
				.withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());
	}

	/**
	 * Get the bean definition for 'dataSource'
	 */
	public static BeanDefinition getDataSourceBeanDefinition() {
		Class<?> beanType = SimpleDataSource.class;
		RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
		beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());
		return beanDefinition;
	}
}
生成的确切代码可能因 Bean 定义的确切性质而异。

上面生成的代码创建了与类等效的 Bean 定义,但以直接的方式创建,如果可能的话,不使用反射。 有一个 bean 定义 和 一个 for 。 当需要实例时,将调用 a。 此供应商在 Bean 上调用该方法。@ConfigurationdataSourceConfigurationdataSourceBeandatasourceBeanInstanceSupplierdataSource()dataSourceConfigurationSpring中文文档

如果 Bean 实现了接口,则 Bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础结构 Bean 实现,例如具有有限依赖关系且已在 Bean 工厂生命周期早期初始化的 Bean。 如果使用工厂方法注册了这样的 Bean,请确保该方法不必初始化其封闭类。BeanFactoryInitializationAotProcessorBeanFactoryPostProcessor@Beanstatic@ConfigurationSpring中文文档

如果 Bean 实现了接口,则 Bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础结构 Bean 实现,例如具有有限依赖关系且已在 Bean 工厂生命周期早期初始化的 Bean。 如果使用工厂方法注册了这样的 Bean,请确保该方法不必初始化其封闭类。BeanRegistrationAotProcessorBeanFactoryPostProcessor@Beanstatic@ConfigurationSpring中文文档

生成的确切代码可能因 Bean 定义的确切性质而异。

最佳实践

AOT 引擎旨在处理尽可能多的用例,而无需更改应用程序中的代码。 但是,请记住,一些优化是在构建时基于 Bean 的静态定义进行的。Spring中文文档

本部分列出了确保应用程序已准备好进行 AOT 的最佳做法。Spring中文文档

公开最精确的 Bean 类型

虽然应用程序可能与 Bean 实现的接口进行交互,但声明最精确的类型仍然非常重要。 AOT 引擎对 Bean 类型执行其他检查,例如检测成员的存在或生命周期回调方法。@AutowiredSpring中文文档

对于类,请确保工厂方法的返回类型尽可能精确。 请看以下示例:@Configuration@BeanSpring中文文档

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

	@Bean
	public MyInterface myInterface() {
		return new MyImplementation();
	}

}

在上面的示例中,Bean 的声明类型为 。 通常的后处理都不会被考虑在内。 例如,如果上下文应注册带注释的处理程序方法,则不会预先检测到它。myInterfaceMyInterfaceMyImplementationMyImplementationSpring中文文档

上面的示例应按如下方式重写:Spring中文文档

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

	@Bean
	public MyImplementation myInterface() {
		return new MyImplementation();
	}

}

如果以编程方式注册 Bean 定义,请考虑使用 as it allowd,以指定处理泛型的 a。RootBeanBefinitionResolvableTypeSpring中文文档

工厂豆

FactoryBean应谨慎使用,因为它在 Bean 类型分辨率方面引入了一个中间层,这在概念上可能不是必需的。 根据经验,如果实例不保持长期状态,并且在运行时的稍后时间点不需要它,则应将其替换为常规工厂方法,可能使用顶部的适配器层(用于声明性配置目的)。FactoryBeanFactoryBeanSpring中文文档

如果您的实现无法解析对象类型(即 ),则需要格外小心。 请看以下示例:FactoryBeanTSpring中文文档

public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {

}

具体的客户端声明应为客户端提供解析的泛型,如以下示例所示:Spring中文文档

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

	@Bean
	public ClientFactoryBean<MyClient> myClient() {
		return new ClientFactoryBean<>(...);
	}

}

如果 Bean 定义是以编程方式注册的,请确保按照下列步骤操作:FactoryBeanSpring中文文档

  1. 用。RootBeanDefinitionSpring中文文档

  2. 将 设置为类,以便 AOT 知道它是一个中间层。beanClassFactoryBeanSpring中文文档

  3. 将 设置为已解析的泛型,以确保公开最精确的类型。ResolvableTypeSpring中文文档

以下示例展示了一个基本定义:Spring中文文档

RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);

JPA格式

必须预先知道 JPA 持久性单元,才能应用某些优化。请看以下基本示例:Spring中文文档

@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
	LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
	factoryBean.setDataSource(dataSource);
	factoryBean.setPackagesToScan("com.example.app");
	return factoryBean;
}

为确保提前进行扫描,必须声明一个 Bean 并由 工厂 Bean 定义,如以下示例所示:PersistenceManagedTypesSpring中文文档

@Bean
PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
	return new PersistenceManagedTypesScanner(resourceLoader)
			.scan("com.example.app");
}

@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource, PersistenceManagedTypes managedTypes) {
	LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
	factoryBean.setDataSource(dataSource);
	factoryBean.setManagedTypes(managedTypes);
	return factoryBean;
}

运行时提示

与常规 JVM 运行时相比,将应用程序作为本机映像运行需要额外的信息。 例如,GraalVM需要提前知道组件是否使用反射。 同样,除非显式指定,否则类路径资源不会在本机映像中提供。 因此,如果应用程序需要加载资源,则必须从相应的GraalVM本机映像配置文件中引用该资源。Spring中文文档

RuntimeHints API 在运行时收集对反射、资源加载、序列化和 JDK 代理的需求。 以下示例确保可以在运行时在本机映像中的类路径加载:config/app.propertiesSpring中文文档

runtimeHints.resources().registerPattern("config/app.properties");

在 AOT 处理期间会自动处理许多合同。 例如,检查方法的返回类型,如果 Spring 检测到该类型应序列化(通常为 JSON),则添加相关的反射提示。@ControllerSpring中文文档

对于核心容器无法推断的情况,可以通过编程方式注册此类提示。 还为常见用例提供了许多方便的注释。Spring中文文档

@ImportRuntimeHints

RuntimeHintsRegistrar通过实现,您可以获取对 AOT 引擎管理的实例的回调。 可以使用任何 Spring Bean 或工厂方法注册此接口的实现。 在生成时检测并调用实现。RuntimeHints@ImportRuntimeHints@BeanRuntimeHintsRegistrarSpring中文文档

import java.util.Locale;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

@Component
@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class)
public class SpellCheckService {

	public void loadDictionary(Locale locale) {
		ClassPathResource resource = new ClassPathResource("dicts/" + locale.getLanguage() + ".txt");
		//...
	}

	static class SpellCheckServiceRuntimeHints implements RuntimeHintsRegistrar {

		@Override
		public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
			hints.resources().registerPattern("dicts/*");
		}
	}

}

如果可能的话,应尽可能靠近需要提示的组件使用。 这样,如果组件没有被贡献给 ,也不会贡献提示。@ImportRuntimeHintsBeanFactorySpring中文文档

也可以通过添加一个条目来静态注册实现,该条目的键等于接口的完全限定名称。META-INF/spring/aot.factoriesRuntimeHintsRegistrarSpring中文文档

@Reflective

@Reflective提供了一种惯用的方式来标记对注释元素进行反思的必要性。 例如,由于底层实现使用反射调用带注释的方法,因此具有元注释。@EventListener@ReflectiveSpring中文文档

默认情况下,仅考虑 Spring Bean,并为带注释的元素注册调用提示。 这可以通过通过注释指定自定义实现来调整。ReflectiveProcessor@ReflectiveSpring中文文档

库作者可以出于自己的目的重复使用此批注。 如果需要处理除 Spring Bean 以外的组件,则可以检测相关类型并用于处理它们。BeanFactoryInitializationAotProcessorReflectiveRuntimeHintsRegistrarSpring中文文档

@RegisterReflectionForBinding

@RegisterReflectionForBinding 是注册序列化任意类型的需要的专用化。 典型的用例是使用容器无法推断的 DTO,例如在方法主体中使用 Web 客户端。@ReflectiveSpring中文文档

@RegisterReflectionForBinding可以应用于类级别的任何 Spring Bean,但它也可以直接应用于方法、字段或构造函数,以更好地指示实际需要提示的位置。 以下示例注册序列化。AccountSpring中文文档

@Component
public class OrderService {

	@RegisterReflectionForBinding(Account.class)
	public void process(Order order) {
		// ...
	}

}

测试运行时提示

Spring Core 还附带了一个实用程序,用于检查现有提示是否与特定用例匹配。 这可以在您自己的测试中用于验证 a 是否包含预期结果。 我们可以为我们编写一个测试,并确保我们能够在运行时加载字典:RuntimeHintsPredicatesRuntimeHintsRegistrarSpellCheckServiceSpring中文文档

	@Test
	void shouldRegisterResourceHints() {
		RuntimeHints hints = new RuntimeHints();
		new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
		assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
				.accepts(hints);
	}

使用 ,我们可以检查反射、资源、序列化或代理生成提示。 这种方法适用于单元测试,但意味着组件的运行时行为是众所周知的。RuntimeHintsPredicatesSpring中文文档

您可以通过使用 GraalVM 跟踪代理运行应用程序的测试套件(或应用程序本身)来了解有关应用程序的全局运行时行为的详细信息。 该代理将在运行时记录所有需要 GraalVM 提示的相关调用,并将它们写为 JSON 配置文件。Spring中文文档

为了更有针对性的发现和测试,Spring Framework 附带了一个带有核心 AOT 测试实用程序的专用模块。 此模块包含 RuntimeHints 代理,这是一个 Java 代理,用于记录与运行时提示相关的所有方法调用,并帮助您断言给定实例涵盖所有记录的调用。 让我们考虑一个基础设施,我们想测试我们在 AOT 处理阶段提供的提示。"org.springframework:spring-core-test"RuntimeHintsSpring中文文档

import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.ClassUtils;

public class SampleReflection {

	private final Log logger = LogFactory.getLog(SampleReflection.class);

	public void performReflection() {
		try {
			Class<?> springVersion = ClassUtils.forName("org.springframework.core.SpringVersion", null);
			Method getVersion = ClassUtils.getMethod(springVersion, "getVersion");
			String version = (String) getVersion.invoke(null);
			logger.info("Spring version:" + version);
		}
		catch (Exception exc) {
			logger.error("reflection failed", exc);
		}
	}

}

然后,我们可以编写一个单元测试(不需要本地编译)来检查我们贡献的提示:Spring中文文档

import java.util.List;

import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent;
import org.springframework.aot.test.agent.RuntimeHintsInvocations;
import org.springframework.aot.test.agent.RuntimeHintsRecorder;
import org.springframework.core.SpringVersion;

import static org.assertj.core.api.Assertions.assertThat;

// @EnabledIfRuntimeHintsAgent signals that the annotated test class or test
// method is only enabled if the RuntimeHintsAgent is loaded on the current JVM.
// It also tags tests with the "RuntimeHints" JUnit tag.
@EnabledIfRuntimeHintsAgent
class SampleReflectionRuntimeHintsTests {

	@Test
	void shouldRegisterReflectionHints() {
		RuntimeHints runtimeHints = new RuntimeHints();
		// Call a RuntimeHintsRegistrar that contributes hints like:
		runtimeHints.reflection().registerType(SpringVersion.class, typeHint ->
				typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE));

		// Invoke the relevant piece of code we want to test within a recording lambda
		RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
			SampleReflection sample = new SampleReflection();
			sample.performReflection();
		});
		// assert that the recorded invocations are covered by the contributed hints
		assertThat(invocations).match(runtimeHints);
	}

}

如果忘记提供提示,测试将失败,并提供有关调用的一些详细信息:Spring中文文档

org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
INFO: Spring version:6.0.0-SNAPSHOT

Missing <"ReflectionHints"> for invocation <java.lang.Class#forName>
with arguments ["org.springframework.core.SpringVersion",
    false,
    jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].
Stacktrace:
<"org.springframework.util.ClassUtils#forName, Line 284
io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19
io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25

有多种方法可以在构建中配置此 Java 代理,因此请参阅构建工具和测试执行插件的文档。 代理本身可以配置为检测特定包(默认情况下,仅检测)。 您可以在 Spring Framework buildSrc README 文件中找到更多详细信息。org.springframeworkSpring中文文档