对于最新的稳定版本,请使用 Spring Framework 6.2.4spring-doc.cadn.net.cn

提前优化

本章介绍了 Spring 的 Ahead of Time (AOT) 优化。spring-doc.cadn.net.cn

有关特定于集成测试的 AOT 支持,请参阅测试的提前支持spring-doc.cadn.net.cn

Ahead of Time Optimization 简介

Spring 对 AOT 优化的支持旨在检查ApplicationContext在构建时,应用通常在运行时发生的决策和发现逻辑。 这样做可以构建一个应用程序启动安排,该安排更直接,并专注于主要基于 Classpath 和Environment.spring-doc.cadn.net.cn

提前应用此类优化意味着以下限制:spring-doc.cadn.net.cn

  • Classpath 是固定的,并且在构建时完全定义。spring-doc.cadn.net.cn

  • 应用程序中定义的 bean 在运行时不能更改,这意味着:spring-doc.cadn.net.cn

    • @Profile,特别是特定于配置文件的配置,需要在构建时选择,并在启用 AOT 时在运行时自动启用。spring-doc.cadn.net.cn

    • Environment影响 Bean 存在的属性 (@Conditional) 仅在构建时考虑。spring-doc.cadn.net.cn

  • 具有实例提供者(lambda 或方法引用)的 Bean 定义不能提前转换。spring-doc.cadn.net.cn

  • 注册为单例的 Bean (使用registerSingleton,通常来自ConfigurableListableBeanFactory) 也无法提前转换。spring-doc.cadn.net.cn

  • 由于我们不能依赖实例,因此请确保 bean 类型与 可能。spring-doc.cadn.net.cn

另请参阅 Best Practices 部分。

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

目前,AOT 专注于允许使用 GraalVM 将 Spring 应用程序部署为原生镜像。 我们打算在未来几代中支持更多基于 JVM 的使用案例。

AOT 引擎概述

AOT 引擎的入口点,用于处理ApplicationContextApplicationContextAotGenerator.它根据GenericApplicationContext,它表示要优化的应用程序,而GenerationContext:spring-doc.cadn.net.cn

  • 刷新ApplicationContext用于 AOT 处理。与传统的刷新相反,此版本仅创建 bean 定义,而不创建 bean 实例。spring-doc.cadn.net.cn

  • 调用可用的BeanFactoryInitializationAotProcessor实现,并将其贡献应用于GenerationContext. 例如,核心实现遍历所有候选 bean 定义并生成必要的代码来恢复BeanFactory.spring-doc.cadn.net.cn

此过程完成后,GenerationContext将已使用应用程序运行所需的生成代码、资源和类进行更新。 这RuntimeHints实例还可用于生成相关的 GraalVM 原生映像配置文件。spring-doc.cadn.net.cn

ApplicationContextAotGenerator#processAheadOfTime返回ApplicationContextInitializer入口点,该入口点允许使用 AOT 优化启动上下文。spring-doc.cadn.net.cn

以下部分将更详细地介绍这些步骤。spring-doc.cadn.net.cn

AOT 处理的刷新

所有 AOT 处理都支持刷新GenericApplicationContext实现。 应用程序上下文是使用任意数量的入口点创建的,通常采用@Configuration-带注释的类。spring-doc.cadn.net.cn

让我们看一个基本示例:spring-doc.cadn.net.cn

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

使用常规运行时启动此应用程序涉及许多步骤,包括 Classpath 扫描、配置类解析、bean 实例化和生命周期回调处理。 AOT 处理的刷新仅应用定期refresh. AOT 处理可以按如下方式触发:spring-doc.cadn.net.cn

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

在此模式下,BeanFactoryPostProcessor实现照常调用。 这包括 configuration class 解析、import selectors、classpath 扫描等。 这些步骤可确保BeanRegistry包含应用程序的相关 Bean 定义。 如果 Bean 定义由条件(例如@Profile)、这些被评估、 与条件不匹配的 bean 定义在此阶段被丢弃。spring-doc.cadn.net.cn

如果自定义代码需要以编程方式注册额外的 bean,请确保自定义 注册码用途BeanDefinitionRegistry而不是BeanFactory作为唯一的 Bean 定义被考虑在内。一个好的模式是实现ImportBeanDefinitionRegistrar并通过@Import在你的 configuration 类。spring-doc.cadn.net.cn

因为此模式实际上并不创建 bean 实例,所以BeanPostProcessor不会调用实现,但与 AOT 处理相关的特定变体除外。 这些是:spring-doc.cadn.net.cn

  • MergedBeanDefinitionPostProcessor实现对 bean 定义进行后处理以提取其他设置,例如initdestroy方法。spring-doc.cadn.net.cn

  • SmartInstantiationAwareBeanPostProcessor如有必要,实现会确定更精确的 bean 类型。 这可确保创建运行时所需的任何代理。spring-doc.cadn.net.cn

完成此部分后,BeanFactory包含应用程序运行所需的 Bean 定义。它不会触发 bean 实例化,但允许 AOT 引擎检查将在运行时创建的 bean。spring-doc.cadn.net.cn

Bean Factory 初始化 AOT 贡献

想要参与此步骤的组件可以实现BeanFactoryInitializationAotProcessor接口。 每个实现都可以根据 bean 工厂的状态返回 AOT 贡献。spring-doc.cadn.net.cn

AOT 贡献是一个组件,它贡献了生成的代码,该代码可重现特定行为。 它还可以做出贡献RuntimeHints以指示需要反射、资源加载、序列化或 JDK 代理。spring-doc.cadn.net.cn

一个BeanFactoryInitializationAotProcessorimplementation 可以在META-INF/spring/aot.factories其键等于接口的完全限定名称。spring-doc.cadn.net.cn

BeanFactoryInitializationAotProcessor接口也可以由 bean 直接实现。 在此模式下,Bean 提供的 AOT 贡献相当于它通过常规运行时提供的功能。 因此,这样的 bean 会自动从 AOT 优化的上下文中排除。spring-doc.cadn.net.cn

如果 Bean 实现了BeanFactoryInitializationAotProcessor接口,则 bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础设施 bean 实现,例如BeanFactoryPostProcessor它们具有有限的依赖项,并且已经在 Bean Factory 生命周期的早期进行了初始化。 如果这样的 bean 是使用@Beanfactory 方法,请确保该方法为static因此,它的@Configurationclass 不必初始化。spring-doc.cadn.net.cn

Bean 注册 AOT 贡献

一个核心BeanFactoryInitializationAotProcessorimplementation 负责为每个 candidate 收集必要的贡献BeanDefinition. 它使用专用的BeanRegistrationAotProcessor.spring-doc.cadn.net.cn

该接口的使用方法如下:spring-doc.cadn.net.cn

如果 Bean 实现了BeanRegistrationAotProcessor接口,则 bean 及其所有依赖项将在 AOT 处理期间初始化。 我们通常建议此接口仅由基础设施 bean 实现,例如BeanFactoryPostProcessor它们具有有限的依赖项,并且已经在 Bean Factory 生命周期的早期进行了初始化。 如果这样的 bean 是使用@Beanfactory 方法,请确保该方法为static因此,它的@Configurationclass 不必初始化。spring-doc.cadn.net.cn

如果没有BeanRegistrationAotProcessor处理特定的已注册 bean,则默认实现会处理它。 这是默认行为,因为为 Bean 定义调整生成的代码应仅限于极端情况。spring-doc.cadn.net.cn

以前面的示例为例,我们假设DataSourceConfiguration如下:spring-doc.cadn.net.cn

@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {

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

}
@Configuration(proxyBeanMethods = false)
class DataSourceConfiguration {

	@Bean
	fun dataSource() = SimpleDataSource()

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

由于这个类没有任何特定条件,因此dataSourceConfigurationdataSource被确定为候选人。 AOT 引擎会将上述配置类转换为类似于以下内容的代码:spring-doc.cadn.net.cn

/**
 * 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自 如果需要排除它们,例如通过静态分析工具,请识别它们。

上面生成的代码创建了等效于@Configuration类,但以直接的方式,并且尽可能不使用反射。 有一个 bean 定义dataSourceConfiguration一个用于dataSourceBean. 当datasourceinstance 是必需的,则BeanInstanceSupplier被调用。 此供应商调用dataSource()方法上的dataSourceConfiguration豆。spring-doc.cadn.net.cn

使用 AOT 优化运行

AOT 是将 Spring 应用程序转换为本机可执行文件的强制性步骤,因此它 在此模式下运行时会自动启用。可以使用这些优化 在 JVM 上,通过设置spring.aot.enabledSystem 属性设置为true.spring-doc.cadn.net.cn

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

最佳实践

AOT 引擎旨在处理尽可能多的使用案例,而无需更改应用程序中的代码。 但是,请记住,一些优化是在构建时根据 bean 的静态定义进行的。spring-doc.cadn.net.cn

本节列出了确保您的应用程序已准备好进行 AOT 的最佳实践。spring-doc.cadn.net.cn

编程 Bean 注册

AOT 引擎负责@Configurationmodel 和任何可能是 作为处理配置的一部分调用。如果您需要额外注册 bean 中,请确保使用BeanDefinitionRegistry注册 bean 定义。spring-doc.cadn.net.cn

这通常可以通过BeanDefinitionRegistryPostProcessor.请注意,如果它 将自身注册为 Bean,它将在运行时再次调用,除非你使 一定要实施BeanFactoryInitializationAotProcessor也。一个更地道的 方法是实施ImportBeanDefinitionRegistrar并使用@Import上 您的配置类之一。这会在配置过程中调用您的自定义代码 类解析。spring-doc.cadn.net.cn

如果你使用不同的回调以编程方式声明其他 bean,那么它们是 可能不会由 AOT 引擎处理,因此不会有任何提示 为他们生成。根据环境,这些 bean 可能未在 都。例如,类路径扫描在本机映像中不起作用,因为没有 classpath 的概念。对于此类情况,扫描在 构建时间。spring-doc.cadn.net.cn

公开最精确的 Bean 类型

虽然您的应用程序可能与 Bean 实现的接口进行交互,但声明最精确的类型仍然非常重要。 AOT 引擎对 Bean 类型执行其他检查,例如检测是否存在@Autowiredmembers 或生命周期回调方法。spring-doc.cadn.net.cn

@Configuration类中,请确保 Factory 的 return 类型@Bean方法尽可能精确。 请考虑以下示例:spring-doc.cadn.net.cn

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

在上面的示例中,myInterfacebean 是MyInterface. 通常的后处理都不会MyImplementation考虑。 例如,如果MyImplementation上下文应该注册,则不会预先检测到它。spring-doc.cadn.net.cn

上面的示例应重写如下:spring-doc.cadn.net.cn

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

如果要以编程方式注册 Bean 定义,请考虑使用RootBeanBefinition因为它允许指定ResolvableType处理泛型。spring-doc.cadn.net.cn

避免多个构造函数

容器能够根据多个候选项选择最合适的构造函数来使用。 但是,这不是最佳实践,使用@Autowired如有必要,则首选。spring-doc.cadn.net.cn

如果您正在处理无法修改的代码库,则可以设置preferredConstructors属性以指示应该使用哪个构造函数。spring-doc.cadn.net.cn

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

当制作RootBeanDefinition从编程方式来看,您不受可以使用的类型的限制。 例如,您可能有一个自定义的record具有多个属性,您的 bean 将其作为构造函数参数。spring-doc.cadn.net.cn

虽然这在常规运行时中运行良好,但 AOT 不知道如何生成自定义数据结构的代码。 一个好的经验法则是记住 bean 定义是多个模型之上的抽象。 建议分解为简单类型或引用以这种方式构建的 bean,而不是使用此类结构。spring-doc.cadn.net.cn

作为最后的手段,您可以实施自己的org.springframework.aot.generate.ValueCodeGenerator$Delegate. 要使用它,请在META-INF/spring/aot.factories使用Delegate作为键。spring-doc.cadn.net.cn

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

Spring AOT 检测创建 bean 需要做什么,并使用实例供应商将其转换为生成的代码。 该容器还支持创建具有自定义参数的 bean,这会导致 AOT 出现几个问题:spring-doc.cadn.net.cn

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

  2. 绕过实例供应商意味着创建后的所有其他优化也被跳过。 例如,字段和方法的自动装配将在实例供应商中处理时被跳过。spring-doc.cadn.net.cn

与其使用自定义参数创建原型范围的 bean,不如推荐使用手动工厂模式,其中 bean 负责创建实例。spring-doc.cadn.net.cn

避免循环依赖关系

某些用例可能会导致一个或多个 bean 之间出现循环依赖关系。使用 常规运行时,可以通过@Autowired在 setter 方法或字段上。但是,AOT 优化的上下文将无法以 显式循环依赖关系。spring-doc.cadn.net.cn

因此,在 AOT 优化的应用程序中,您应该努力避免循环 依赖。如果无法做到这一点,您可以使用@Lazy注射点或ObjectProvider以延迟访问或检索必要的协作 bean。有关更多信息,请参阅此提示spring-doc.cadn.net.cn

工厂豆

FactoryBean应谨慎使用,因为它在 bean 类型解析方面引入了一个中间层,这在概念上可能并不必要。 根据经验,如果FactoryBean实例不保持长期状态,并且在运行时的后续时间点不需要,则应将其替换为常规的工厂方法,可能使用FactoryBeanadapter 层(用于声明性配置目的)。spring-doc.cadn.net.cn

如果您的FactoryBeanimplementation 不会解析对象类型(即T),则需要格外小心。 请考虑以下示例:spring-doc.cadn.net.cn

public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
	// ...
}

具体的 client 声明应为 client 提供解析的泛型,如以下示例所示:spring-doc.cadn.net.cn

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

如果FactoryBeanbean 定义以编程方式注册,请确保按照以下步骤作:spring-doc.cadn.net.cn

  1. RootBeanDefinition.spring-doc.cadn.net.cn

  2. beanClassFactoryBean类,以便 AOT 知道它是一个中间层。spring-doc.cadn.net.cn

  3. ResolvableType更改为 resolved generic 中,这可确保公开最精确的类型。spring-doc.cadn.net.cn

以下示例展示了一个基本定义:spring-doc.cadn.net.cn

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

JPA

必须预先知道 JPA 持久性单元,才能应用某些优化。请考虑以下基本示例:spring-doc.cadn.net.cn

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

为了确保提前进行扫描,请PersistenceManagedTypesbean 必须由 Factory bean 定义,如以下示例所示:spring-doc.cadn.net.cn

@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 需要提前知道组件是否使用反射。 同样,除非明确指定,否则 Classpath 资源不会包含在本机映像中。 因此,如果应用程序需要加载资源,则必须从相应的 GraalVM 原生映像配置文件中引用该资源。spring-doc.cadn.net.cn

RuntimeHintsAPI 在运行时收集对反射、资源加载、序列化和 JDK 代理的需求。 以下示例确保config/app.properties可以在运行时从本机映像中的 Classpath 加载:spring-doc.cadn.net.cn

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

在 AOT 处理期间,会自动处理许多合同。 例如,一个@Controller方法,如果 Spring 检测到该类型应该被序列化(通常为 JSON),则会添加相关的反射提示。spring-doc.cadn.net.cn

对于核心容器无法推断的情况,您可以以编程方式注册此类提示。 还为常见使用案例提供了许多方便的注释。spring-doc.cadn.net.cn

@ImportRuntimeHints

RuntimeHintsRegistrar实现允许您获取对RuntimeHints由 AOT 引擎管理的实例。 此接口的实现可以使用@ImportRuntimeHints在任何 Spring bean 上或@BeanFactory 方法。RuntimeHintsRegistrar在构建时检测并调用实现。spring-doc.cadn.net.cn

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/*");
		}
	}

}

If at all possible, @ImportRuntimeHints should be used as close as possible to the component that requires the hints. This way, if the component is not contributed to the BeanFactory, the hints won’t be contributed either.spring-doc.cadn.net.cn

It is also possible to register an implementation statically by adding an entry in META-INF/spring/aot.factories with a key equal to the fully-qualified name of the RuntimeHintsRegistrar interface.spring-doc.cadn.net.cn

@Reflective

@Reflective provides an idiomatic way to flag the need for reflection on an annotated element. For instance, @EventListener is meta-annotated with @Reflective since the underlying implementation invokes the annotated method using reflection.spring-doc.cadn.net.cn

By default, only Spring beans are considered, and an invocation hint is registered for the annotated element. This can be tuned by specifying a custom ReflectiveProcessor implementation via the @Reflective annotation.spring-doc.cadn.net.cn

Library authors can reuse this annotation for their own purposes. If components other than Spring beans need to be processed, a BeanFactoryInitializationAotProcessor can detect the relevant types and use ReflectiveRuntimeHintsRegistrar to process them.spring-doc.cadn.net.cn

@RegisterReflectionForBinding

@RegisterReflectionForBinding is a specialization of @Reflective that registers the need for serializing arbitrary types. A typical use case is the use of DTOs that the container cannot infer, such as using a web client within a method body.spring-doc.cadn.net.cn

@RegisterReflectionForBinding can be applied to any Spring bean at the class level, but it can also be applied directly to a method, field, or constructor to better indicate where the hints are actually required. The following example registers Account for serialization.spring-doc.cadn.net.cn

@Component
public class OrderService {

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

}

Testing Runtime Hints

Spring Core also ships RuntimeHintsPredicates, a utility for checking that existing hints match a particular use case. This can be used in your own tests to validate that a RuntimeHintsRegistrar contains the expected results. We can write a test for our SpellCheckService and ensure that we will be able to load a dictionary at runtime:spring-doc.cadn.net.cn

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

With RuntimeHintsPredicates, we can check for reflection, resource, serialization, or proxy generation hints. This approach works well for unit tests but implies that the runtime behavior of a component is well known.spring-doc.cadn.net.cn

You can learn more about the global runtime behavior of an application by running its test suite (or the app itself) with the GraalVM tracing agent. This agent will record all relevant calls requiring GraalVM hints at runtime and write them out as JSON configuration files.spring-doc.cadn.net.cn

For more targeted discovery and testing, Spring Framework ships a dedicated module with core AOT testing utilities, "org.springframework:spring-core-test". This module contains the RuntimeHints Agent, a Java agent that records all method invocations that are related to runtime hints and helps you to assert that a given RuntimeHints instance covers all recorded invocations. Let’s consider a piece of infrastructure for which we’d like to test the hints we’re contributing during the AOT processing phase.spring-doc.cadn.net.cn

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);
		}
	}

}

We can then write a unit test (no native compilation required) that checks our contributed hints:spring-doc.cadn.net.cn

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);
	}

}

If you forgot to contribute a hint, the test will fail and provide some details about the invocation:spring-doc.cadn.net.cn

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

There are various ways to configure this Java agent in your build, so please refer to the documentation of your build tool and test execution plugin. The agent itself can be configured to instrument specific packages (by default, only org.springframework is instrumented). You’ll find more details in the Spring Framework buildSrc README file.spring-doc.cadn.net.cn