Ahead of Time Optimizations

这些优化有一些限制,例如 classpath 在构建时是固定的,依赖注入、环境属性和 Bean 定义的更改在运行时不被考虑。然而,即使有这些限制,这样做的好处包括:

  • 允许构建时执行提前预处理并生成其他资产,如 Java 源码、字节码和 RuntimeHints。

  • 主要用于使用 GraalVM 将 Spring 应用程序部署为原生映像。

  • 启用 AOT 处理刷新,仅创建 bean 定义,而不进行 bean 实例化。

  • 允许 BeanFactoryInitializationAotProcessor 实现为 AOT 贡献生成代码,指示反射、资源加载、序列化和 JDK 代理的需要。

  • 启用 BeanRegistrationAotProcessor 实现为特定已注册 bean 处理 AOT 贡献。

  • 运行时提示指示 GraalVM 本机映像配置文件中的反射、资源加载、序列化和 JDK 代理的需要。

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

This chapter covers Spring’s Ahead of Time (AOT) optimizations.

有关集成测试的专有 AOT 支持,请参阅 Ahead of Time Support for Tests

For AOT support specific to integration tests, see Ahead of Time Support for Tests.

Introduction to Ahead of Time Optimizations

Spring 对 AOT 优化的支持旨在构建时检查 ApplicationContext,并应用通常在运行时发生的决策和发现逻辑。这样做可以构建一个应用程序启动安排,它更加直接,并基于 classpath 和 Environment 主要集中于一组固定功能。

Spring’s support for AOT optimizations is meant to inspect an ApplicationContext at build time and apply decisions and discovery logic that usually happens at runtime. Doing so allows building an application startup arrangement that is more straightforward and focused on a fixed set of features based mainly on the classpath and the Environment.

在开发阶段应用此类优化意味着以下限制:

Applying such optimizations early implies the following restrictions:

  • The classpath is fixed and fully defined at build time.

  • The beans defined in your application cannot change at runtime, meaning:

    • @Profile, in particular profile-specific configuration, needs to be chosen at build time and is automatically enabled at runtime when AOT is enabled.

    • Environment properties that impact the presence of a bean (@Conditional) are only considered at build time.

  • Bean definitions with instance suppliers (lambdas or method references) cannot be transformed ahead-of-time.

  • Beans registered as singletons (using registerSingleton, typically from ConfigurableListableBeanFactory) cannot be transformed ahead-of-time either.

  • As we cannot rely on the instance, make sure that the bean type is as precise as possible.

另请参阅 Best Practices 部分。

See also the Best Practices section.

当这些限制存在时,可以在构建时执行提前处理并生成其他资产。A Spring AOT 处理的应用程序通常会生成:

When these restrictions are in place, it becomes possible to perform ahead-of-time processing at build time and generate additional assets. A Spring AOT processed application typically generates:

  • Java source code

  • Bytecode (usually for dynamic proxies)

  • RuntimeHints for the use of reflection, resource loading, serialization, and JDK proxies

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

At the moment, AOT is focused on allowing Spring applications to be deployed as native images using GraalVM. We intend to support more JVM-based use cases in future generations.

AOT engine overview

用于处理 ApplicationContext 的 AOT 引擎的入口点是 ApplicationContextAotGenerator。它根据表示要优化的应用程序和 GenerationContextGenericApplicationContext 执行以下步骤:

The entry point of the AOT engine for processing an ApplicationContext is ApplicationContextAotGenerator. It takes care of the following steps, based on a GenericApplicationContext that represents the application to optimize and a GenerationContext:

  • Refresh an ApplicationContext for AOT processing. Contrary to a traditional refresh, this version only creates bean definitions, not bean instances.

  • Invoke the available BeanFactoryInitializationAotProcessor implementations and apply their contributions against the GenerationContext. For instance, a core implementation iterates over all candidate bean definitions and generates the necessary code to restore the state of the BeanFactory.

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

Once this process completes, the GenerationContext will have been updated with the generated code, resources, and classes that are necessary for the application to run. The RuntimeHints instance can also be used to generate the relevant GraalVM native image configuration files.

ApplicationContextAotGenerator#processAheadOfTime 返回 ApplicationContextInitializer 入口点的类名,该入口点允许在 AOT 优化中启动上下文。

ApplicationContextAotGenerator#processAheadOfTime returns the class name of the ApplicationContextInitializer entry point that allows the context to be started with AOT optimizations.

这些步骤将在以下部分中得到更详细的介绍。

Those steps are covered in greater detail in the sections below.

Refresh for AOT Processing

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

Refresh for AOT processing is supported on all GenericApplicationContext implementations. An application context is created with any number of entry points, usually in the form of @Configuration-annotated classes.

我们来看一个基本示例:

Let’s look at a basic example:

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

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

Starting this application with the regular runtime involves a number of steps including classpath scanning, configuration class parsing, bean instantiation, and lifecycle callback handling. Refresh for AOT processing only applies a subset of what happens with a regular refresh. AOT processing can be triggered as follows:

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

在此模式中,"@7" 照常调用。这包括配置类解析、导入选择器、类路径扫描等。这些步骤确保 "@5" 包含应用程序的相关 Bean 定义。如果 Bean 定义受条件(例如 "@6")保护,这些条件就会被评估,并且不匹配其条件的 Bean 定义在这个阶段就会被舍弃。

In this mode, BeanFactoryPostProcessor implementations are invoked as usual. This includes configuration class parsing, import selectors, classpath scanning, etc. Such steps make sure that the BeanRegistry contains the relevant bean definitions for the application. If bean definitions are guarded by conditions (such as @Profile), these are evaluated, and bean definitions that don’t match their conditions are discarded at this stage.

如果自定义代码需要以编程方式注册额外的 bean,请确保自定义注册代码使用 BeanDefinitionRegistry 而非 BeanFactory,因为只考虑 bean 定义。一个好的模式是实现 ImportBeanDefinitionRegistrar 并通过对配置类的 @Import 注册它。

If custom code needs to register extra beans programmatically, make sure that custom registration code uses BeanDefinitionRegistry instead of BeanFactory as only bean definitions are taken into account. A good pattern is to implement ImportBeanDefinitionRegistrar and register it via an @Import on one of your configuration classes.

由于此模式实际上并未创建 bean 实例,因此不会调用 BeanPostProcessor 实现,AOT 处理相关的特定变量除外。它们是:

Because this mode does not actually create bean instances, BeanPostProcessor implementations are not invoked, except for specific variants that are relevant for AOT processing. These are:

  • MergedBeanDefinitionPostProcessor implementations post-process bean definitions to extract additional settings, such as init and destroy methods.

  • SmartInstantiationAwareBeanPostProcessor implementations determine a more precise bean type if necessary. This makes sure to create any proxy that will be required at runtime.

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

Once this part completes, the BeanFactory contains the bean definitions that are necessary for the application to run. It does not trigger bean instantiation but allows the AOT engine to inspect the beans that will be created at runtime.

Bean Factory Initialization AOT Contributions

希望参与此步骤的组件可以实现 BeanFactoryInitializationAotProcessor 接口。每个实现可以根据 bean 工厂的状态返回一个 AOT 贡献。

Components that want to participate in this step can implement the BeanFactoryInitializationAotProcessor interface. Each implementation can return an AOT contribution, based on the state of the bean factory.

AOT 贡献是一个组件,它生成重现某一特定行为的代码。它还可以提供 RuntimeHints 以指示反射、资源加载、序列化或 JDK 代理的需要。

An AOT contribution is a component that contributes generated code which reproduces a particular behavior. It can also contribute RuntimeHints to indicate the need for reflection, resource loading, serialization, or JDK proxies.

可以将 BeanFactoryInitializationAotProcessor 实现与等于接口完全限定名的键一起注册到 META-INF/spring/aot.factories 中。

A BeanFactoryInitializationAotProcessor implementation can be registered in META-INF/spring/aot.factories with a key equal to the fully-qualified name of the interface.

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

The BeanFactoryInitializationAotProcessor interface can also be implemented directly by a bean. In this mode, the bean provides an AOT contribution equivalent to the feature it provides with a regular runtime. Consequently, such a bean is automatically excluded from the AOT-optimized context.

如果 bean 实现 BeanFactoryInitializationAotProcessor 接口,则在 AOT 处理期间将初始化该 bean 及其 所有 依赖项。我们通常建议只有基础架构 bean(例如 BeanFactoryPostProcessor)实现此接口,此类 bean 具有有限的依赖项并且已在 bean 工厂生命周期的早期进行初始化。如果使用 @Bean 工厂方法注册此类 bean,请确保该方法为 static,以便不必初始化其包含的 @Configuration 类。

If a bean implements the BeanFactoryInitializationAotProcessor interface, the bean and all of its dependencies will be initialized during AOT processing. We generally recommend that this interface is only implemented by infrastructure beans such as BeanFactoryPostProcessor which have limited dependencies and are already initialized early in the bean factory lifecycle. If such a bean is registered using an @Bean factory method, ensure the method is static so that its enclosing @Configuration class does not have to be initialized.

Bean Registration AOT Contributions

核心 BeanFactoryInitializationAotProcessor 实现负责收集每个候选 BeanDefinition 的必要贡献。它使用专用的 BeanRegistrationAotProcessor 这样做。

A core BeanFactoryInitializationAotProcessor implementation is responsible for collecting the necessary contributions for each candidate BeanDefinition. It does so using a dedicated BeanRegistrationAotProcessor.

此接口的使用方式如下:

This interface is used as follows:

  • Implemented by a BeanPostProcessor bean, to replace its runtime behavior. For instance AutowiredAnnotationBeanPostProcessor implements this interface to generate code that injects members annotated with @Autowired.

  • Implemented by a type registered in META-INF/spring/aot.factories with a key equal to the fully-qualified name of the interface. Typically used when the bean definition needs to be tuned for specific features of the core framework.

如果 bean 实现 BeanRegistrationAotProcessor 接口,则在 AOT 处理期间将初始化该 bean 及其 所有 依赖项。我们通常建议只有基础架构 bean(例如 BeanFactoryPostProcessor)实现此接口,此类 bean 具有有限的依赖项并且已在 bean 工厂生命周期的早期进行初始化。如果使用 @Bean 工厂方法注册此类 bean,请确保该方法为 static,以便不必初始化其包含的 @Configuration 类。

If a bean implements the BeanRegistrationAotProcessor interface, the bean and all of its dependencies will be initialized during AOT processing. We generally recommend that this interface is only implemented by infrastructure beans such as BeanFactoryPostProcessor which have limited dependencies and are already initialized early in the bean factory lifecycle. If such a bean is registered using an @Bean factory method, ensure the method is static so that its enclosing @Configuration class does not have to be initialized.

如果没有 BeanRegistrationAotProcessor 处理特定已注册 bean,则默认实现将处理它。这是默认行为,因为针对 bean 定义调整生成代码应仅限于特殊情况。

If no BeanRegistrationAotProcessor handles a particular registered bean, a default implementation processes it. This is the default behavior, since tuning the generated code for a bean definition should be restricted to corner cases.

取我们的前面示例,我们假设 DataSourceConfiguration 如下:

Taking our previous example, let’s assume that DataSourceConfiguration is as follows:

  • Java

  • Kotlin

@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {

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

}
@Configuration(proxyBeanMethods = false)
class DataSourceConfiguration {

	@Bean
	fun dataSource() = SimpleDataSource()

}

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

Kotlin class names with backticks that use invalid Java identifiers (not starting with a letter, containing spaces, etc.) are not supported.

由于此类没有任何特定条件,因此 dataSourceConfigurationdataSource 被标识为候选。AOT 引擎将上述配置类转换成类似于以下的代码:

Since there isn’t any particular condition on this class, dataSourceConfiguration and dataSource are identified as candidates. The AOT engine will convert the configuration class above to code similar to the following:

  • Java

/**
 * 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 定义的具体性质而有所不同。

The exact code generated may differ depending on the exact nature of your bean definitions.

每个生成的类都用 org.springframework.aot.generate.Generated 注解,以识别它们是否需要排除,例如通过静态分析工具。

Each generated class is annotated with org.springframework.aot.generate.Generated to identify them if they need to be excluded, for instance by static analysis tools.

以上生成的代码创建了等效于 @Configuration 类的 bean 定义,但如果可能的话,它是以直接方式且无需反射来完成的。dataSourceConfiguration 有一个 bean 定义,dataSourceBean 也有一个。当需要 datasource 实例时,将调用 BeanInstanceSupplier。此 Suppler 在 dataSourceConfiguration bean 上调用 dataSource() 方法。

The generated code above creates bean definitions equivalent to the @Configuration class, but in a direct way and without the use of reflection if at all possible. There is a bean definition for dataSourceConfiguration and one for dataSourceBean. When a datasource instance is required, a BeanInstanceSupplier is called. This supplier invokes the dataSource() method on the dataSourceConfiguration bean.

Running with AOT optimizations

AOT 是将 Spring 应用程序转换为原生可执行文件的强制步骤,因此在以这种模式运行时将自动启用它。通过将 spring.aot.enabled 系统属性设置为 true,可以在 JVM 上使用这些优化。

AOT is a mandatory step to transform a Spring application to a native executable, so it is automatically enabled when running in this mode. It is possible to use those optimizations on the JVM by setting the spring.aot.enabled System property to true.

当加入 AOT 优化时,在构建时做出的某些决策会硬编码在应用程序设置中。例如,在构建时启用的配置文件也会在运行时自动启用。

When AOT optimizations are included, some decisions that have been taken at build-time are hard-coded in the application setup. For instance, profiles that have been enabled at build-time are automatically enabled at runtime as well.

Best Practices

AOT 引擎旨在处理尽可能多的用例,且应用程序中不需要进行代码更改。但是,请记住,某些优化是在构建时基于 bean 的静态定义进行的。

The AOT engine is designed to handle as many use cases as possible, with no code change in applications. However, keep in mind that some optimizations are made at build time based on a static definition of the beans.

本节列出了可以确保您的应用程序已准备好进行 AOT 的最佳实践。

This section lists the best practices that make sure your application is ready for AOT.

Programmatic bean registration

AOT 引擎负责 @Configuration 模型以及在处理配置过程中可能调用的任何回调。如果您需要以编程方式注册附加 bean,请务必使用 BeanDefinitionRegistry 注册 bean 定义。

The AOT engine takes care of the @Configuration model and any callback that might be invoked as part of processing your configuration. If you need to register additional beans programmatically, make sure to use a BeanDefinitionRegistry to register bean definitions.

这通常可以通过 BeanDefinitionRegistryPostProcessor 完成。请注意,如果它本身已注册为 bean,则除非您也确实实现了 BeanFactoryInitializationAotProcessor,否则它将在运行时再次被调用。更惯用的方法是实现 ImportBeanDefinitionRegistrar,并使用其中一个配置类上的 @Import 注册它。这会将您的自定义代码作为配置类分析的一部分进行调用。

This can typically be done via a BeanDefinitionRegistryPostProcessor. Note that, if it is registered itself as a bean, it will be invoked again at runtime unless you make sure to implement BeanFactoryInitializationAotProcessor as well. A more idiomatic way is to implement ImportBeanDefinitionRegistrar and register it using @Import on one of your configuration classes. This invokes your custom code as part of configuration class parsing.

如果您使用不同的回调以编程方式声明附加 bean,则 AOT 引擎可能不会处理它们,因此不会为它们生成提示。根据环境,这些 bean 可能会根本没有注册。例如,类路径扫描在原生映像中不起作用,因为没有类路径的概念。对于这种情况,在构建时进行扫描至关重要。

If you declare additional beans programmatically using a different callback, they are likely not going to be handled by the AOT engine, and therefore no hints are going to be generated for them. Depending on the environment, those beans may not be registered at all. For instance, classpath scanning does not work in a native image as there is no notion of a classpath. For cases like this, it is crucial that the scanning happens at build time.

Expose The Most Precise Bean Type

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

While your application may interact with an interface that a bean implements, it is still very important to declare the most precise type. The AOT engine performs additional checks on the bean type, such as detecting the presence of @Autowired members or lifecycle callback methods.

对于 @Configuration 类,请确保工厂 @Bean 方法的返回类型尽可能精确。考虑以下示例:

For @Configuration classes, make sure that the return type of the factory @Bean method is as precise as possible. Consider the following example:

  • Java

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

在上述示例中,myInterface bean 的声明类型为 MyInterface。对于 MyImplementation,后续处理都不会将其考虑在内。例如,如果 MyImplementation 上有注释处理程序方法而上下文应该注册该方法,则不会预先检测到它。

In the example above, the declared type for the myInterface bean is MyInterface. None of the usual post-processing will take MyImplementation into account. For instance, if there is an annotated handler method on MyImplementation that the context should register, it won’t be detected upfront.

上述示例应重写为如下形式:

The example above should be rewritten as follows:

  • Java

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

如果您以编程方式注册 bean 定义,请考虑使用 RootBeanBefinition,因为它允许指定处理泛型的 ResolvableType

If you are registering bean definitions programmatically, consider using RootBeanBefinition as it allows to specify a ResolvableType that handles generics.

Avoid Multiple Constructors

该容器能够基于几个候选对象选择最合适的构造函数。然而,这不是最佳实践,如果必要,通过 @Autowired 标记首选构造函数更好。

The container is able to choose the most appropriate constructor to use based on several candidates. However, this is not a best practice and flagging the preferred constructor with @Autowired if necessary is preferred.

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

In case you are working on a code base that you cannot modify, you can set the preferredConstructors attribute on the related bean definition to indicate which constructor should be used.

Avoid Complex Data Structure for Constructor Parameters and Properties

以编程方式创建 RootBeanDefinition 时,您不受可使用的类型的限制。例如,您的 Bean 可以采用包含几个属性的自定义 record 作为构造函数参数。

When crafting a RootBeanDefinition programmatically, you are not constrained in terms of types that you can use. For instance, you may have a custom record with several properties that your bean takes as a constructor argument.

虽然这在常规运行时中运行良好,但 AOT 不知道如何生成自定义数据结构的代码。一条好经验是记住 Bean 定义是基于几个模型的抽象。建议分解为简单类型或引用如此构建的 Bean,而不是使用这种结构。

While this works fine with the regular runtime, AOT does not know how to generate the code of your custom data structure. A good rule of thumb is to keep in mind that bean definitions are an abstraction on top of several models. Rather than using such structure, decomposing to simple types or referring to a bean that is built as such is recommended.

作为最后手段,您可以实现您自己的 org.springframework.aot.generate.ValueCodeGenerator$Delegate。要使用它,请使用 Delegate 作为键将其完全限定的名称注册在 META-INF/spring/aot.factories 中。

As a last resort, you can implement your own org.springframework.aot.generate.ValueCodeGenerator$Delegate. To use it, register its fully qualified name in META-INF/spring/aot.factories using the Delegate as the key.

Avoid Creating Bean with Custom Arguments

Spring AOT 检测创建 bean 所需的操作,并使用实例供应商将其转换为生成代码中。容器还支持使用 自定义参数 创建 bean,这会导致 AOT 出现多个问题:

Spring AOT detects what needs to be done to create a bean and translates that in generated code using an instance supplier. The container also supports creating a bean with custom arguments that leads to several issues with AOT:

  1. The custom arguments require dynamic introspection of a matching constructor or factory method. Those arguments cannot be detected by AOT, so the necessary reflection hints will have to be provided manually.

  2. By-passing the instance supplier means that all other optimizations after creation are skipped as well. For instance, autowiring on fields and methods will be skipped as they are handled in the instance supplier.

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

Rather than having prototype-scoped beans created with custom arguments, we recommend a manual factory pattern where a bean is responsible for the creation of the instance.

FactoryBean

FactoryBean 应该谨慎使用,因为它引入了 Bean 类型解析方面的中间层,这在概念上可能并不必要。作为经验法则,如果 FactoryBean 实例不保留长期状态,并且在运行时以后不必要,则应该用常规工厂方法替换它,可能在上面有一个 FactoryBean 适配器层(用于声明式配置目的)。

FactoryBean should be used with care as it introduces an intermediate layer in terms of bean type resolution that may not be conceptually necessary. As a rule of thumb, if the FactoryBean instance does not hold long-term state and is not needed at a later point in time at runtime, it should be replaced by a regular factory method, possibly with a FactoryBean adapter layer on top (for declarative configuration purposes).

如果您的 FactoryBean 实现未解析对象类型(即 T),则需要特别小心。请考虑以下示例:

If your FactoryBean implementation does not resolve the object type (i.e. T), extra care is necessary. Consider the following example:

  • Java

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

具体客户端声明应为客户端提供已解析的泛型,如下例所示:

A concrete client declaration should provide a resolved generic for the client, as shown in the following example:

  • Java

@Configuration(proxyBeanMethods = false)
public class UserConfiguration {

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

}

如果 FactoryBean Bean 定义以编程方式注册,请确保执行以下步骤:

If the FactoryBean bean definition is registered programmatically, make sure to follow these steps:

  1. Use RootBeanDefinition.

  2. Set the beanClass to the FactoryBean class so that AOT knows that it is an intermediate layer.

  3. Set the ResolvableType to a resolved generic, which makes sure the most precise type is exposed.

以下示例展示了一个基本定义:

The following example showcases a basic definition:

  • Java

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

JPA

为应用某些优化,必须预先了解 JPA 持久性单元。请考虑以下基本示例:

The JPA persistence unit has to be known upfront for certain optimizations to apply. Consider the following basic example:

  • Java

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

要确保扫描提前发生,必须声明 PersistenceManagedTypes Bean,并由工厂 Bean 定义使用,如下例所示:

To make sure the scanning occurs ahead of time, a PersistenceManagedTypes bean must be declared and used by the factory bean definition, as shown by the following example:

  • Java

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

Runtime Hints

与常规的 JVM 运行时相比,以本机映像运行应用程序需要更多信息。例如,GraalVM 需要提前知道组件是否使用反射。同样,除非明确指定,否则类路径资源不会包含在本机映像中。因此,如果应用程序需要加载资源,则必须从相应的 GraalVM 本机映像配置文件中引用它。

Running an application as a native image requires additional information compared to a regular JVM runtime. For instance, GraalVM needs to know ahead of time if a component uses reflection. Similarly, classpath resources are not included in a native image unless specified explicitly. Consequently, if the application needs to load a resource, it must be referenced from the corresponding GraalVM native image configuration file.

RuntimeHints API 在运行时收集对反射、资源加载、序列化和 JDK 代理的需求。以下示例确保可以在原生镜像中的运行时从类路径加载 config/app.properties

The RuntimeHints API collects the need for reflection, resource loading, serialization, and JDK proxies at runtime. The following example makes sure that config/app.properties can be loaded from the classpath at runtime within a native image:

  • Java

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

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

A number of contracts are handled automatically during AOT processing. For instance, the return type of a @Controller method is inspected, and relevant reflection hints are added if Spring detects that the type should be serialized (typically to JSON).

对于核心容器无法推断的情况,您可以以编程方式注册此类提示。还提供了一些方便的注释,适用于常见用例。

For cases that the core container cannot infer, you can register such hints programmatically. A number of convenient annotations are also provided for common use cases.

@ImportRuntimeHints

RuntimeHintsRegistrar 实现允许您回叫到 AOT 引擎管理的 RuntimeHints 实例。可以使用 @ImportRuntimeHints 在任何 Spring Bean 或 @Bean 工厂方法上注册此接口的实现。RuntimeHintsRegistrar 实现会在构建时被检测并调用。

RuntimeHintsRegistrar implementations allow you to get a callback to the RuntimeHints instance managed by the AOT engine. Implementations of this interface can be registered using @ImportRuntimeHints on any Spring bean or @Bean factory method. RuntimeHintsRegistrar implementations are detected and invoked at build time.

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

}

如果可能,@ImportRuntimeHints 应尽可能靠近需要提示的组件使用。这样,如果组件没有添加到 BeanFactory,提示也不会添加。

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.

也可以在`META-INF/spring/aot.factories`中添加一项条目来静态注册一个实现,其键等于`RuntimeHintsRegistrar`接口的完全限定名称。

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.

@Reflective

@Reflective 提供了一种惯用方式来标记对注释元素进行反射的需求。例如,@EventListener 使用 @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 bean,并且为带注释的元素注册了调用提示。这可以通过`@Reflective`注释指定自定义`ReflectiveProcessor`实现来调整。

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 Bean 以外的组件,BeanFactoryInitializationAotProcessor 可以检测相关类型,并使用 ReflectiveRuntimeHintsRegistrar 来处理它们。

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.

@RegisterReflectionForBinding

@RegisterReflectionForBinding@Reflective 的专业化,它注册了对序列化任意类型的需求。典型用例是使用容器无法推断的 DTO,例如在方法体中使用 Web 客户端。

@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.

@RegisterReflectionForBinding 可以在类级别应用到任何 Spring bean,但也可以直接应用到方法、域或构造函数,以便更好地表明提示实际需要的在哪里。以下示例注册 Account 进行序列化。

@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.

  • Java

@Component
public class OrderService {

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

}

Testing Runtime Hints

Spring Core 也随附 RuntimeHintsPredicates,这是一种用来检查现有提示是否匹配特定用例的实用工具。这可以在你自己的测试中用来验证 RuntimeHintsRegistrar 是否包含预期结果。我们可以为我们的 SpellCheckService 编写一个测试,确保我们能够在运行时加载字典:

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:

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

使用`RuntimeHintsPredicates`,我们可以检查反射、资源、串行化或代理生成提示。这种方法非常适合单元测试,但前提是组件的运行时行为是已知的。

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.

通过使用 GraalVM 追踪代理 和应用程序的测试套件(或应用程序本身)运行该代理,可以了解应用程序的全局运行时行为。该代理将记录在运行时需要 GraalVM 提示的所有相关调用,并将记录写入 JSON 配置文件。

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 Framework 提供了一个专门的模块,其中包含核心 AOT 测试实用工具“org.springframework:spring-core-test”。此模块包含 RuntimeHints Agent,这是一个 Java 代理,它会记录与运行时提示相关的所有方法调用,并帮助你断言给定的 RuntimeHints 实例涵盖了所有记录的调用。让我们考虑一个基础架构,我们想要测试在 AOT 处理阶段期间所贡献提示。

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.

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:

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:

org.springframework.docs.core.aot.hints.testing.SampleReflection performReflection
INFO: Spring version: 6.2.0

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 代理,请参阅构建工具和测试执行插件的文档。该代理本身可以配置为检测特定的包(默认情况下,仅 org.springframework 受到检测)。有关更多详情,请参阅 Spring Framework buildSrc 自述文件

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.