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

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

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

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

提前优化简介

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

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

  • 类路径是固定的,并在生成时完全定义。Spring中文文档

  • 应用程序中定义的 Bean 不能在运行时更改,这意味着:Spring中文文档

    • @Profile,特别是特定于配置文件的配置,需要在构建时选择,并在启用 AOT 时在运行时自动启用。Spring中文文档

    • Environment影响 Bean () 存在的属性仅在构建时考虑。@ConditionalSpring中文文档

  • 无法提前转换具有实例供应商(lambda 或方法引用)的 Bean 定义。Spring中文文档

  • 注册为单例的 Bean (使用 ,通常来自 )也无法提前转换。registerSingletonConfigurableListableBeanFactorySpring中文文档

  • 由于我们不能依赖实例,因此请确保 Bean 类型与 可能。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 定义受条件(例如 )保护,则对这些条件进行评估, 在此阶段,与其条件不匹配的 Bean 定义将被丢弃。BeanRegistry@ProfileSpring中文文档

如果自定义代码需要以编程方式注册额外的 Bean,请确保自定义 注册码使用而不是仅作为 Bean 定义也被考虑在内。一个好的模式是通过 on one of your 配置类。BeanDefinitionRegistryBeanFactoryImportBeanDefinitionRegistrar@ImportSpring中文文档

由于此模式实际上不会创建 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中文文档

该接口也可以直接由 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();
	}

}
@Configuration(proxyBeanMethods = false)
class DataSourceConfiguration {

	@Bean
	fun dataSource() = SimpleDataSource()

}
不支持带有反引号的 Kotlin 类名,这些类名使用无效的 Java 标识符(不以字母开头、包含空格等)。

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

/**
 * Bean definitions for {@link DataSourceConfiguration}
 */
@Generated
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 定义的确切性质而异。
每个生成的类都用 如果需要排除它们,例如通过静态分析工具,请识别它们。org.springframework.aot.generate.Generated

上面生成的代码创建了与类等效的 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中文文档

不支持带有反引号的 Kotlin 类名,这些类名使用无效的 Java 标识符(不以字母开头、包含空格等)。
生成的确切代码可能因 Bean 定义的确切性质而异。
每个生成的类都用 如果需要排除它们,例如通过静态分析工具,请识别它们。org.springframework.aot.generate.Generated

使用 AOT 优化运行

AOT 是将 Spring 应用程序转换为本机可执行文件的强制性步骤,因此它 在此模式下运行时自动启用。可以使用这些优化 在 JVM 上,将 System 属性设置为 。spring.aot.enabledtrueSpring中文文档

当包括 AOT 优化时,在构建时做出的一些决策 在应用程序设置中硬编码。例如,已在 构建时也会在运行时自动启用。
当包括 AOT 优化时,在构建时做出的一些决策 在应用程序设置中硬编码。例如,已在 构建时也会在运行时自动启用。

最佳实践

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

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

程序化 Bean 注册

AOT 引擎负责处理模型和任何可能 在处理配置时调用。如果您需要注册其他 bean 以编程方式,请确保使用 a 进行注册 Bean 定义。@ConfigurationBeanDefinitionRegistrySpring中文文档

这通常可以通过 .请注意,如果它 将自身注册为 bean,它将在运行时再次调用,除非您使 一定要实施。一个更惯用的 方法是使用 ON 实现和注册它 您的配置类之一。这将在配置过程中调用自定义代码 类解析。BeanDefinitionRegistryPostProcessorBeanFactoryInitializationAotProcessorImportBeanDefinitionRegistrar@ImportSpring中文文档

如果您使用不同的回调以编程方式声明其他 Bean,则它们是 AOT 引擎可能不会处理,因此不会有任何提示 为他们生成。根据环境的不同,这些 Bean 可能不会在 都。例如,类路径扫描在本机映像中不起作用,因为没有 类路径的概念。对于此类情况,扫描必须在 构建时间。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中文文档

避免使用多个构造函数

容器能够根据多个候选项选择最合适的构造函数来使用。 但是,这不是最佳做法,最好在必要时标记首选构造函数。@AutowiredSpring中文文档

如果您正在处理无法修改的代码库,则可以在相关 Bean 定义上设置 preferredConstructors 属性,以指示应使用哪个构造函数。Spring中文文档

避免构造函数参数和属性的复杂数据结构

以编程方式制作时,在可以使用的类型方面不受限制。 例如,您可能有一个自定义项,其中包含多个属性,您的 Bean 将这些属性用作构造函数参数。RootBeanDefinitionrecordSpring中文文档

虽然这在常规运行时工作正常,但 AOT 不知道如何生成自定义数据结构的代码。 一个好的经验法则是记住,Bean 定义是建立在几个模型之上的抽象。 与其使用这种结构,不如将分解为简单的类型或引用以这种方式构建的 Bean。Spring中文文档

作为最后的手段,您可以实现自己的. 要使用它,请在 using as the key 中注册其完全限定名称。org.springframework.aot.generate.ValueCodeGenerator$DelegateMETA-INF/spring/aot.factoriesDelegateSpring中文文档

避免使用自定义参数创建 Bean

Spring AOT 检测创建 Bean 需要做什么,并使用实例供应商将其转换为生成的代码。 该容器还支持使用自定义参数创建 Bean,这会导致 AOT 出现几个问题:Spring中文文档

  1. 自定义参数需要对匹配的构造函数或工厂方法进行动态自省。 AOT 无法检测到这些参数,因此必须手动提供必要的反射提示。Spring中文文档

  2. 绕过实例供应商意味着创建后的所有其他优化也将被跳过。 例如,字段和方法上的自动布线将被跳过,因为它们在实例供应商中处理。Spring中文文档

我们建议使用手动工厂模式,其中 Bean 负责创建实例,而不是使用自定义参数创建原型范围的 Bean。Spring中文文档

工厂豆

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中文文档