提前优化
本章介绍 Spring 的提前 (AOT) 优化。
有关集成测试特定的 AOT 支持,请参阅 测试的提前支持。
提前优化介绍
Spring 对 AOT 优化的支持旨在在构建时检查 ApplicationContext
,并应用通常在运行时发生的决策和发现逻辑。这样做可以构建更直接的应用程序启动安排,主要基于类路径和环境,专注于固定的一组功能。
提早应用此类优化意味着以下限制
-
类路径在构建时是固定的且完全定义。
-
应用程序中定义的 bean 在运行时不能改变,这意味着
-
@Profile
,特别是针对特定配置文件的配置,需要在构建时选择,并在启用 AOT 时在运行时自动启用。 -
影响 bean 是否存在的
Environment
属性 (@Conditional
) 只在构建时考虑。
-
-
带有实例供应商(lambda 或方法引用)的 Bean 定义无法提前转换。
-
注册为单例(通常使用
ConfigurableListableBeanFactory
的registerSingleton
)的 Bean 也无法提前转换。 -
由于我们不能依赖实例,请确保 Bean 类型尽可能精确。
另请参阅最佳实践部分。 |
当存在这些限制时,就可以在构建时执行提前处理并生成额外的资产。经过 Spring AOT 处理的应用程序通常会生成
-
Java 源代码
-
字节码(通常用于动态代理)
-
RuntimeHints
用于反射、资源加载、序列化和 JDK 代理
目前,AOT 主要用于允许 Spring 应用程序使用 GraalVM 部署为原生镜像。我们打算在未来的版本中支持更多基于 JVM 的用例。 |
AOT 引擎概述
AOT 引擎处理 ApplicationContext
的入口点是 ApplicationContextAotGenerator
。它基于代表要优化的应用程序的 GenericApplicationContext
和一个 GenerationContext
来处理以下步骤
-
刷新
ApplicationContext
以进行 AOT 处理。与传统的刷新不同,此版本只创建 bean 定义,而不创建 bean 实例。 -
调用可用的
BeanFactoryInitializationAotProcessor
实现,并将其贡献应用于GenerationContext
。例如,一个核心实现会遍历所有候选 bean 定义,并生成必要的代码来恢复BeanFactory
的状态。
此过程完成后,GenerationContext
将更新生成的代码、资源和应用程序运行所需的类。RuntimeHints
实例还可以用于生成相关的 GraalVM 原生镜像配置文件。
ApplicationContextAotGenerator#processAheadOfTime
返回 ApplicationContextInitializer
入口点的类名,该入口点允许在启用 AOT 优化的情况下启动上下文。
以下各节将更详细地介绍这些步骤。
AOT 处理的刷新
所有 GenericApplicationContext
实现都支持 AOT 处理的刷新。可以使用任意数量的入口点创建应用程序上下文,通常是 @Configuration
注解的类。
我们来看一个基本示例
@Configuration(proxyBeanMethods=false)
@ComponentScan
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
public class MyApplication {
}
使用常规运行时启动此应用程序涉及多个步骤,包括类路径扫描、配置类解析、bean 实例化和生命周期回调处理。AOT 处理的刷新只应用常规 refresh
的一部分。可以按如下方式触发 AOT 处理
RuntimeHints hints = new RuntimeHints();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyApplication.class);
context.refreshForAotProcessing(hints);
// ...
context.close();
在此模式下,BeanFactoryPostProcessor
实现会照常调用。这包括配置类解析、import 选择器、类路径扫描等。这些步骤确保 BeanRegistry
包含应用程序相关的 bean 定义。如果 bean 定义受条件(例如 @Profile
)的保护,则会评估这些条件,不符合条件的 bean 定义在此阶段会被丢弃。
如果自定义代码需要编程式地注册额外的 bean,请确保自定义注册代码使用 BeanDefinitionRegistry
而不是 BeanFactory
,因为只考虑 bean 定义。一个好的模式是实现 ImportBeanDefinitionRegistrar
并通过 @Import
在其中一个配置类上注册它。
因为此模式不实际创建 bean 实例,所以 BeanPostProcessor
实现不会被调用,除了与 AOT 处理相关的特定变体。这些是
-
MergedBeanDefinitionPostProcessor
实现后置处理 bean 定义,以提取额外设置,例如init
和destroy
方法。 -
SmartInstantiationAwareBeanPostProcessor
实现在必要时确定更精确的 bean 类型。这确保创建在运行时所需的任何代理。
这部分完成后,BeanFactory
包含应用程序运行所需的 bean 定义。它不触发 bean 实例化,但允许 AOT 引擎检查将在运行时创建的 bean。
Bean Factory 初始化 AOT 贡献
想要参与此步骤的组件可以实现 BeanFactoryInitializationAotProcessor
接口。每个实现都可以基于 bean 工厂的状态返回一个 AOT 贡献。
AOT 贡献是贡献生成代码的组件,这些代码复制了特定行为。它还可以贡献 RuntimeHints
,以指示对反射、资源加载、序列化或 JDK 代理的需求。
BeanFactoryInitializationAotProcessor
实现可以在 META-INF/spring/aot.factories
中注册,键等于接口的完全限定名。
BeanFactoryInitializationAotProcessor
接口也可以直接由 bean 实现。在此模式下,该 bean 提供的 AOT 贡献等同于其在常规运行时提供的功能。因此,此类 bean 会自动从 AOT 优化后的上下文中排除。
如果 bean 实现了 |
Bean 注册 AOT 贡献
核心 BeanFactoryInitializationAotProcessor
实现负责收集每个候选 BeanDefinition
的必要贡献。它通过专用的 BeanRegistrationAotProcessor
来完成。
此接口的使用方式如下
-
由
BeanPostProcessor
bean 实现,以替换其运行时行为。例如AutowiredAnnotationBeanPostProcessor
实现了此接口,以生成注入带有@Autowired
注解的成员的代码。 -
由在
META-INF/spring/aot.factories
中注册的类型实现,键等于接口的完全限定名。通常用于需要针对核心框架的特定功能调整 bean 定义的情况。
如果 bean 实现了 |
如果没有 BeanRegistrationAotProcessor
处理特定的已注册 bean,则由默认实现处理。这是默认行为,因为针对 bean 定义生成的代码调整应仅限于特殊情况。
以上一个示例为例,假设 DataSourceConfiguration
如下所示
-
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 类名。 |
由于此类上没有任何特定条件,因此 dataSourceConfiguration
和 dataSource
被识别为候选者。AOT 引擎会将上面的配置类转换为类似以下的代码
-
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 定义的具体性质而异。 |
每个生成的类都使用 org.springframework.aot.generate.Generated 进行注解,以便在需要排除它们时(例如由静态分析工具)进行识别。 |
上面生成的代码创建的 bean 定义等同于 @Configuration
类,但采用直接方式,并且尽可能不使用反射。有一个用于 dataSourceConfiguration
的 bean 定义,还有一个用于 dataSourceBean
。
当需要 datasource 实例时,会调用 BeanInstanceSupplier
。此 supplier 会调用 dataSource()
方法在 dataSourceConfiguration
bean 上。
当包含 AOT 优化时,一些在构建时做出的决策会硬编码到应用程序设置中。
例如,在构建时启用的 profile 在运行时也会自动启用。
AOT 引擎旨在处理尽可能多的用例,而无需更改应用程序代码。但是,请记住,一些优化是在构建时基于 bean 的静态定义进行的。
本节列出了确保应用程序为 AOT 做好准备的最佳实践。
AOT 引擎会处理 @Configuration
模型以及作为处理配置一部分可能被调用的任何回调。如果需要编程式注册额外的 bean,请确保使用 BeanDefinitionRegistry
来注册 bean 定义。
这通常可以通过 BeanDefinitionRegistryPostProcessor
完成。请注意,如果它自己被注册为一个 bean,除非您也实现了 BeanFactoryInitializationAotProcessor
,否则它会在运行时再次被调用。更地道的方式是实现 ImportBeanDefinitionRegistrar
并通过 @Import
在其中一个配置类上注册它。
这会在配置类解析过程中调用您的自定义代码。
如果使用不同的回调编程式声明额外的 bean,它们很可能不会被 AOT 引擎处理,因此不会为它们生成任何提示。根据环境的不同,这些 bean 可能根本不会被注册。例如,类路径扫描在原生镜像中不起作用,因为没有类路径的概念。对于这种情况,扫描在构建时发生至关重要。
-
Java
-
Kotlin
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyInterface myInterface() {
return new MyImplementation();
}
}
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myInterface(): MyInterface = MyImplementation()
}
虽然您的应用程序可能与 bean 实现的接口交互,但声明最精确的类型仍然非常重要。AOT 引擎会对 bean 类型执行额外检查,例如检测 @Autowired
成员或生命周期回调方法是否存在。
-
Java
-
Kotlin
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyImplementation myInterface() {
return new MyImplementation();
}
}
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myInterface() = MyImplementation()
}
对于 @Configuration
类,请确保工厂 @Bean
方法的返回类型尽可能精确。考虑以下示例
在上面的示例中,myInterface
bean 的声明类型是 MyInterface
。通常的后处理都不会考虑 MyImplementation
。
例如,如果 MyImplementation
上有一个带有注解的处理器方法,上下文应该注册它,但它不会提前被检测到。
如果您正在处理一个无法修改的代码库,可以在相关的 bean 定义上设置 preferredConstructors
属性来指示应使用哪个构造函数。
避免将复杂数据结构用于构造函数参数和属性
在以编程方式创建 RootBeanDefinition
时,您在使用类型方面不受限制。例如,您可能有一个带有多个属性的自定义 record
,您的 bean 将其作为构造函数参数。
虽然这在常规运行时环境中工作良好,但 AOT 不知道如何生成您的自定义数据结构的代码。一个好的经验法则是记住 bean 定义是构建在多种模型之上的抽象。建议不要使用此类结构,而是分解为简单类型或引用以这种方式构建的 bean。
作为最后手段,您可以实现自己的 org.springframework.aot.generate.ValueCodeGenerator$Delegate
。要使用它,请在 META-INF/spring/aot.factories
中以 Delegate
为键注册其完全限定名。
避免使用自定义参数创建 Bean
Spring AOT 检测创建 bean 需要做什么,并使用实例 supplier 将其转换为生成的代码。容器也支持使用自定义参数创建 bean,这会导致 AOT 出现一些问题。
-
自定义参数需要对匹配的构造函数或工厂方法进行动态自省。AOT 无法检测到这些参数,因此必须手动提供必要的反射提示。
-
绕过实例 supplier 意味着创建后的所有其他优化也会被跳过。例如,字段和方法上的自动装配将被跳过,因为它们是在实例 supplier 中处理的。
与其使用自定义参数创建 prototype 范围的 bean,我们建议采用手动工厂模式,其中一个 bean 负责创建实例。
避免循环依赖
某些用例可能导致一个或多个 bean 之间出现循环依赖。在常规运行时环境中,可能可以通过在 setter 方法或字段上使用 @Autowired
来连接这些循环依赖。然而,AOT 优化的上下文在存在显式循环依赖时将无法启动。
因此,在 AOT 优化的应用程序中,应努力避免循环依赖。如果无法避免,可以使用 @Lazy
注入点或 ObjectProvider
来延迟访问或检索必要的协作 bean。有关更多信息,请参阅此提示。
FactoryBean
应谨慎使用 FactoryBean
,因为它在 bean 类型解析方面引入了一个中间层,这可能在概念上不是必需的。根据经验,如果 FactoryBean
实例不持有长期状态且在运行时后期不需要,则应将其替换为常规工厂方法,顶部可以加上 FactoryBean
适配器层(用于声明式配置目的)。
如果您的 FactoryBean
实现没有解析对象类型(即 T
),则需要格外小心。考虑以下示例
-
Java
-
Kotlin
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
// ...
}
class ClientFactoryBean<T : AbstractClient> : FactoryBean<T> {
// ...
}
具体客户端声明应为客户端提供已解析的泛型,如以下示例所示
-
Java
-
Kotlin
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public ClientFactoryBean<MyClient> myClient() {
return new ClientFactoryBean<>(...);
}
}
@Configuration(proxyBeanMethods = false)
class UserConfiguration {
@Bean
fun myClient() = ClientFactoryBean<MyClient>(...)
}
如果以编程方式注册 FactoryBean
bean 定义,请确保遵循以下步骤
-
使用
RootBeanDefinition
。 -
将
beanClass
设置为FactoryBean
类,以便 AOT 知道它是一个中间层。 -
将
ResolvableType
设置为已解析的泛型,这确保暴露最精确的类型。
以下示例展示了一个基本定义
-
Java
-
Kotlin
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
val beanDefinition = RootBeanDefinition(ClientFactoryBean::class.java)
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean::class.java, MyClient::class.java));
// ...
registry.registerBeanDefinition("myClient", beanDefinition)
JPA
必须事先知道 JPA 持久化单元,以便应用某些优化。考虑以下基本示例
-
Java
-
Kotlin
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("com.example.app");
return factoryBean;
}
@Bean
fun customDBEntityManagerFactory(dataSource: DataSource): LocalContainerEntityManagerFactoryBean {
val factoryBean = LocalContainerEntityManagerFactoryBean()
factoryBean.dataSource = dataSource
factoryBean.setPackagesToScan("com.example.app")
return factoryBean
}
为确保提前进行扫描,必须声明一个 PersistenceManagedTypes
bean 并由工厂 bean 定义使用,如以下示例所示
-
Java
-
Kotlin
@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;
}
@Bean
fun persistenceManagedTypes(resourceLoader: ResourceLoader): PersistenceManagedTypes {
return PersistenceManagedTypesScanner(resourceLoader)
.scan("com.example.app")
}
@Bean
fun customDBEntityManagerFactory(dataSource: DataSource, managedTypes: PersistenceManagedTypes): LocalContainerEntityManagerFactoryBean {
val factoryBean = LocalContainerEntityManagerFactoryBean()
factoryBean.dataSource = dataSource
factoryBean.setManagedTypes(managedTypes)
return factoryBean
}
运行时提示
与常规 JVM 运行时相比,将应用程序作为本地镜像运行需要额外信息。例如,GraalVM 需要事先知道组件是否使用了反射。类似地,除非明确指定,否则类路径资源不会包含在本地镜像中。因此,如果应用程序需要加载资源,必须从相应的 GraalVM 本地镜像配置文件中引用它。
RuntimeHints
API 收集运行时对反射、资源加载、序列化和 JDK 代理的需求。以下示例确保在本地镜像中,config/app.properties
可以在运行时从类路径加载。
-
Java
-
Kotlin
runtimeHints.resources().registerPattern("config/app.properties");
runtimeHints.resources().registerPattern("config/app.properties")
在 AOT 处理期间会自动处理许多契约。例如,会检查 @Controller
方法的返回类型,如果 Spring 检测到该类型应被序列化(通常为 JSON),则会添加相关的反射提示。
对于核心容器无法推断的情况,您可以以编程方式注册此类提示。还提供了一些方便的注解用于常见用例。
@ImportRuntimeHints
RuntimeHintsRegistrar
实现允许您获得 AOT 引擎管理的 RuntimeHints
实例的回调。可以通过在任何 Spring bean 或 @Bean
工厂方法上使用 @ImportRuntimeHints
来注册此接口的实现。RuntimeHintsRegistrar
实在构建时被检测和调用。
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
中,这些提示也不会被贡献。
也可以通过在 META-INF/spring/aot.factories
中添加一个条目来静态注册实现,该条目的键等于 RuntimeHintsRegistrar
接口的完全限定名。
@Reflective
@Reflective
提供了一种惯用的方式来标记带注解元素上对反射的需求。例如,@EventListener
被 @Reflective
元注解,因为底层实现使用反射调用带注解的方法。
开箱即用,只有 Spring bean 会被考虑,但您可以使用 @ReflectiveScan
选择启用扫描。在下面的示例中,将考虑 com.example.app
包及其子包的所有类型
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ReflectiveScan;
@Configuration
@ReflectiveScan("com.example.app")
public class MyConfiguration {
}
扫描在 AOT 处理期间进行,目标包中的类型不需要具有类级注解即可被考虑。这将执行“深度扫描”,并检查类型、字段、构造函数、方法和封闭元素上是否存在 @Reflective
(直接或作为元注解)。
默认情况下,@Reflective
为带注解的元素注册一个调用提示。可以通过 @Reflective
注解指定自定义的 ReflectiveProcessor
实现来调整此行为。
库作者可以出于自身目的重用此注解。此类自定义的示例在下一节中介绍。
@RegisterReflection
@RegisterReflection
是 @Reflective
的一个特殊化,提供了一种声明性方式来为任意类型注册反射。
作为 @Reflective 的特殊化,如果您使用 @ReflectiveScan ,它也会被检测到。 |
在以下示例中,可以通过反射调用 AccountService
上的公共构造函数和公共方法。
@Configuration
@RegisterReflection(classes = AccountService.class, memberCategories =
{ MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS })
class MyConfiguration {
}
@RegisterReflection
可以应用于类级别的任何目标类型,但也可以直接应用于方法,以便更好地指示实际需要提示的位置。
@RegisterReflection
可以用作元注解以提供更具体的需求。 @RegisterReflectionForBinding
就是这样一种组合注解,它注册了对任意类型进行序列化的需求。一个典型用例是使用容器无法推断的 DTO,例如在方法体中使用 web 客户端。
以下示例注册 Order
用于序列化。
@Component
class OrderService {
@RegisterReflectionForBinding(Order.class)
public void process(Order order) {
// ...
}
}
这会为 Order
的构造函数、字段、属性和 record 组件注册提示。还会为属性和 record 组件上过渡使用的类型注册提示。换句话说,如果 Order
暴露其他类型,也会为这些类型注册提示。
测试运行时提示
Spring Core 还提供了 RuntimeHintsPredicates
,这是一个用于检查现有提示是否匹配特定用例的实用工具。可以在您自己的测试中使用它来验证 RuntimeHintsRegistrar
包含预期的结果。我们可以为 SpellCheckService
编写测试,并确保能够在运行时加载字典。
@Test
void shouldRegisterResourceHints() {
RuntimeHints hints = new RuntimeHints();
new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
.accepts(hints);
}
使用 RuntimeHintsPredicates
,我们可以检查反射、资源、序列化或代理生成提示。这种方法适用于单元测试,但意味着组件的运行时行为是已知晓的。
通过使用 GraalVM tracing agent 运行应用程序的测试套件(或应用程序本身),可以了解更多关于应用程序的全局运行时行为。该 agent 将记录运行时所有需要 GraalVM 提示的相关调用,并将其写入 JSON 配置文件。
为了进行更有针对性的发现和测试,Spring Framework 提供了核心 AOT 测试实用工具的专用模块,"org.springframework:spring-core-test"
。此模块包含 RuntimeHints Agent,它是一个 Java agent,用于记录所有与运行时提示相关的方法调用,并帮助您断言给定的 RuntimeHints
实例覆盖了所有记录的调用。让我们考虑一个基础设施片段,我们想测试在 AOT 处理阶段贡献的提示。
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);
}
}
}
然后我们可以编写一个单元测试(无需本地编译)来检查我们贡献的提示
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);
}
}
如果您忘记贡献提示,测试将失败并提供有关调用的一些详细信息
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 agent,因此请参阅您的构建工具和测试执行插件的文档。agent 本身可以配置为检测特定包(默认情况下,仅检测 org.springframework
)。您将在 Spring Framework buildSrc
README 文件中找到更多详细信息。