此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
提前优化
本章介绍了 Spring 的 Ahead of Time (AOT) 优化。
有关特定于集成测试的 AOT 支持,请参阅测试的提前支持。
Ahead of Time Optimization 简介
Spring 对 AOT 优化的支持旨在检查构建时并应用通常在运行时发生的决策和发现逻辑。
这样做可以构建一个应用程序启动安排,该安排更直接,并专注于主要基于 Classpath 和 的一组固定功能。ApplicationContext
Environment
提前应用此类优化意味着以下限制:
-
Classpath 是固定的,并且在构建时完全定义。
-
应用程序中定义的 bean 在运行时不能更改,这意味着:
-
@Profile
,特别是特定于配置文件的配置,需要在构建时选择,并在启用 AOT 时在运行时自动启用。 -
Environment
影响 bean () 存在的属性仅在构建时考虑。@Conditional
-
-
具有实例提供者(lambda 或方法引用)的 Bean 定义不能提前转换。
-
注册为单例的 bean (使用 , 通常 from )也不能提前转换。
registerSingleton
ConfigurableListableBeanFactory
-
由于我们不能依赖实例,因此请确保 bean 类型与 可能。
另请参阅 Best Practices 部分。 |
当这些限制到位时,就可以在构建时执行提前处理并生成其他资产。 Spring AOT 处理的应用程序通常生成:
-
Java 源代码
-
字节码(通常用于动态代理)
-
RuntimeHints
用于使用反射、资源加载、序列化和 JDK 代理
目前,AOT 专注于允许使用 GraalVM 将 Spring 应用程序部署为原生镜像。 我们打算在未来几代中支持更多基于 JVM 的使用案例。 |
AOT 引擎概述
用于处理 的 AOT 引擎的入口点是 。它基于表示要优化的应用程序的 a 和 GenerationContext
负责以下步骤:ApplicationContext
ApplicationContextAotGenerator
GenericApplicationContext
-
刷新 an 以进行 AOT 处理。与传统的刷新相反,此版本仅创建 bean 定义,而不创建 bean 实例。
ApplicationContext
-
调用可用的实现,并对其贡献应用它们对 . 例如,核心实现迭代所有候选 bean 定义并生成必要的代码来恢复 .
BeanFactoryInitializationAotProcessor
GenerationContext
BeanFactory
此过程完成后,将使用应用程序运行所需的生成代码、资源和类进行更新。
该实例还可用于生成相关的 GraalVM 原生映像配置文件。GenerationContext
RuntimeHints
ApplicationContextAotGenerator#processAheadOfTime
返回允许使用 AOT 优化启动上下文的入口点的类名。ApplicationContextInitializer
以下部分将更详细地介绍这些步骤。
AOT 处理的刷新
所有实施都支持 AOT 处理的刷新。
应用程序上下文是使用任意数量的入口点创建的,通常采用 -annotated 类的形式。GenericApplicationContext
@Configuration
让我们看一个基本示例:
@Configuration(proxyBeanMethods=false)
@ComponentScan
@Import({DataSourceConfiguration.class, ContainerConfiguration.class})
public class MyApplication {
}
使用常规运行时启动此应用程序涉及许多步骤,包括 Classpath 扫描、配置类解析、bean 实例化和生命周期回调处理。
AOT 处理的刷新仅应用常规刷新
的子集。
AOT 处理可以按如下方式触发:
RuntimeHints hints = new RuntimeHints();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyApplication.class);
context.refreshForAotProcessing(hints);
// ...
context.close();
在这种模式下,BeanFactoryPostProcessor
实现像往常一样被调用。
这包括 configuration class 解析、import selectors、classpath 扫描等。
这些步骤确保 包含应用程序的相关 bean 定义。
如果 bean 定义由条件(例如 )保护,则对这些
与条件不匹配的 bean 定义在此阶段被丢弃。BeanRegistry
@Profile
如果自定义代码需要以编程方式注册额外的 bean,请确保自定义
注册码使用 而不是仅作为 Bean
定义被考虑在内。一个好的模式是通过
configuration 类。BeanDefinitionRegistry
BeanFactory
ImportBeanDefinitionRegistrar
@Import
因为这种模式实际上并不创建 bean 实例,所以除了与 AOT 处理相关的特定变体外,不会调用实现。
这些是:BeanPostProcessor
-
MergedBeanDefinitionPostProcessor
实现对 Bean 定义进行后处理以提取其他设置,例如 AND 方法。init
destroy
-
SmartInstantiationAwareBeanPostProcessor
如有必要,实现会确定更精确的 bean 类型。 这可确保创建运行时所需的任何代理。
完成此部分后,将包含应用程序运行所需的 Bean 定义。它不会触发 bean 实例化,但允许 AOT 引擎检查将在运行时创建的 bean。BeanFactory
Bean Factory 初始化 AOT 贡献
想要参与此步骤的组件可以实现BeanFactoryInitializationAotProcessor
接口。
每个实现都可以根据 bean 工厂的状态返回 AOT 贡献。
AOT 贡献是一个组件,它贡献了生成的代码,该代码可重现特定行为。
它还有助于指示需要反射、资源加载、序列化或 JDK 代理。RuntimeHints
可以使用等于接口的完全限定名称的密钥来注册 implementation 。BeanFactoryInitializationAotProcessor
META-INF/spring/aot.factories
该接口也可以由 bean 直接实现。
在此模式下,Bean 提供的 AOT 贡献相当于它通过常规运行时提供的功能。
因此,这样的 bean 会自动从 AOT 优化的上下文中排除。BeanFactoryInitializationAotProcessor
如果 Bean 实现了该接口,则 Bean 及其所有依赖项将在 AOT 处理期间进行初始化。
我们通常建议此接口仅由基础结构 bean 实现,例如,这些 bean 具有有限的依赖项,并且已经在 bean 工厂生命周期的早期进行了初始化。
如果这样的 Bean 是使用工厂方法注册的,请确保该方法不必初始化其封闭类。 |
Bean 注册 AOT 贡献
核心实施负责收集每个候选者的必要贡献。
它使用专用的 .BeanFactoryInitializationAotProcessor
BeanDefinition
BeanRegistrationAotProcessor
该接口的使用方法如下:
-
由 Bean 实现,以替换其运行时行为。 例如
,AutowiredAnnotationBeanPostProcessor
实现此接口以生成注入带有 .BeanPostProcessor
@Autowired
-
由注册 in 的类型实现,其键等于接口的完全限定名称。 通常在需要针对核心框架的特定功能调整 bean 定义时使用。
META-INF/spring/aot.factories
如果 Bean 实现了该接口,则 Bean 及其所有依赖项将在 AOT 处理期间进行初始化。
我们通常建议此接口仅由基础结构 bean 实现,例如,这些 bean 具有有限的依赖项,并且已经在 bean 工厂生命周期的早期进行了初始化。
如果这样的 Bean 是使用工厂方法注册的,请确保该方法不必初始化其封闭类。 |
如果 no 处理特定的已注册 bean,则默认实现会处理它。
这是默认行为,因为为 Bean 定义调整生成的代码应仅限于极端情况。BeanRegistrationAotProcessor
以前面的示例为例,我们假设如下所示: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 类名。 |
由于该类没有任何特定条件,因此被标识为候选项。
AOT 引擎会将上述配置类转换为类似于以下内容的代码:dataSourceConfiguration
dataSource
-
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 定义的确切性质,生成的确切代码可能会有所不同。 |
每个生成的类都用 to 进行注释
如果需要排除它们,例如通过静态分析工具,请识别它们。org.springframework.aot.generate.Generated |
上面生成的代码创建了等效于类的 bean 定义,但以直接的方式创建,并且尽可能不使用反射。
有一个 bean 定义和一个 。
当需要一个实例时,将调用 a。
此供应商在 Bean 上调用该方法。@Configuration
dataSourceConfiguration
dataSourceBean
datasource
BeanInstanceSupplier
dataSource()
dataSourceConfiguration
使用 AOT 优化运行
AOT 是将 Spring 应用程序转换为本机可执行文件的强制性步骤,因此它
在此模式下运行时会自动启用。可以使用这些优化
通过将 System 属性设置为 。spring.aot.enabled
true
当包含 AOT 优化时,在构建时做出的一些决策 在应用程序设置中进行硬编码。例如,已在 build-time 也会在运行时自动启用。 |
最佳实践
AOT 引擎旨在处理尽可能多的使用案例,而无需更改应用程序中的代码。 但是,请记住,一些优化是在构建时根据 bean 的静态定义进行的。
本节列出了确保您的应用程序已准备好进行 AOT 的最佳实践。
编程 Bean 注册
AOT 引擎负责模型和任何可能的回调
作为处理配置的一部分调用。如果您需要额外注册
bean 中,请确保使用 a 来注册
bean 定义。@Configuration
BeanDefinitionRegistry
这通常可以通过 .请注意,如果它
将自身注册为 Bean,它将在运行时再次调用,除非你使
肯定也会实施。一个更地道的
方法是使用 on 实现并注册它
您的配置类之一。这会在配置过程中调用您的自定义代码
类解析。BeanDefinitionRegistryPostProcessor
BeanFactoryInitializationAotProcessor
ImportBeanDefinitionRegistrar
@Import
如果你使用不同的回调以编程方式声明其他 bean,那么它们是 可能不会由 AOT 引擎处理,因此不会有任何提示 为他们生成。根据环境,这些 bean 可能未在 都。例如,类路径扫描在本机映像中不起作用,因为没有 classpath 的概念。对于此类情况,扫描在 构建时间。
公开最精确的 Bean 类型
虽然您的应用程序可能与 Bean 实现的接口进行交互,但声明最精确的类型仍然非常重要。
AOT 引擎对 Bean 类型执行其他检查,例如检测成员或生命周期回调方法的存在。@Autowired
对于类,请确保工厂方法的返回类型尽可能精确。
请考虑以下示例:@Configuration
@Bean
-
Java
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyInterface myInterface() {
return new MyImplementation();
}
}
在上面的示例中,bean 的声明类型是 。
通常的后处理都不会考虑。
例如,如果上下文应该注册一个带注释的处理程序方法,则不会预先检测到它。myInterface
MyInterface
MyImplementation
MyImplementation
上面的示例应重写如下:
-
Java
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public MyImplementation myInterface() {
return new MyImplementation();
}
}
如果要以编程方式注册 bean 定义,请考虑使用 因为它允许指定处理泛型的 a。RootBeanBefinition
ResolvableType
避免多个构造函数
容器能够根据多个候选项选择最合适的构造函数来使用。
但是,这不是最佳做法,如有必要,最好使用 标记首选构造函数。@Autowired
如果您正在处理无法修改的代码库,则可以在相关的 bean 定义上设置 preferredConstructors
属性以指示应该使用哪个构造函数。
避免构造函数参数和属性使用复杂的数据结构
以编程方式制作 时,您不受可以使用的类型的限制。
例如,您可能有一个自定义项,其中包含多个属性,您的 bean 将其作为构造函数参数。RootBeanDefinition
record
虽然这在常规运行时中运行良好,但 AOT 不知道如何生成自定义数据结构的代码。 一个好的经验法则是记住 bean 定义是多个模型之上的抽象。 建议分解为简单类型或引用以这种方式构建的 bean,而不是使用此类结构。
作为最后的手段,您可以实施自己的 .
要使用它,请使用 作为键注册其完全限定名称。org.springframework.aot.generate.ValueCodeGenerator$Delegate
META-INF/spring/aot.factories
Delegate
避免使用自定义参数创建 Bean
Spring AOT 检测创建 bean 需要做什么,并使用实例供应商将其转换为生成的代码。 该容器还支持创建具有自定义参数的 bean,这会导致 AOT 出现几个问题:
-
自定义参数需要对匹配的构造函数或工厂方法进行动态内省。 AOT 无法检测到这些参数,因此必须手动提供必要的反射提示。
-
绕过实例供应商意味着创建后的所有其他优化也被跳过。 例如,字段和方法的自动装配将在实例供应商中处理时被跳过。
与其使用自定义参数创建原型范围的 bean,不如推荐使用手动工厂模式,其中 bean 负责创建实例。
避免循环依赖关系
某些用例可能会导致一个或多个 bean 之间出现循环依赖关系。使用
常规运行时,可以通过 on setter 方法或字段连接这些循环依赖项。但是,AOT 优化的上下文将无法以
显式循环依赖关系。@Autowired
因此,在 AOT 优化的应用程序中,您应该努力避免循环
依赖。如果这是不可能的,你可以使用注入点或延迟访问或检索必要的协作 bean。有关更多信息,请参阅此提示。@Lazy
ObjectProvider
工厂豆
FactoryBean
应谨慎使用,因为它在 bean 类型解析方面引入了一个中间层,这在概念上可能并不必要。
根据经验,如果实例不保持长期状态并且在运行时的后续时间点不需要,则应将其替换为常规工厂方法,可能在顶部有一个适配器层(用于声明性配置目的)。FactoryBean
FactoryBean
如果您的 implementation 没有解析 object type (i.),则需要格外小心。
请考虑以下示例:FactoryBean
T
-
Java
public class ClientFactoryBean<T extends AbstractClient> implements FactoryBean<T> {
// ...
}
具体的 client 声明应为 client 提供解析的泛型,如以下示例所示:
-
Java
@Configuration(proxyBeanMethods = false)
public class UserConfiguration {
@Bean
public ClientFactoryBean<MyClient> myClient() {
return new ClientFactoryBean<>(...);
}
}
如果 Bean 定义是以编程方式注册的,请确保按照以下步骤操作:FactoryBean
-
用。
RootBeanDefinition
-
将 设置为 类,以便 AOT 知道它是一个中间层。
beanClass
FactoryBean
-
将 the 设置为 resolved generic ,以确保公开最精确的类型。
ResolvableType
以下示例展示了一个基本定义:
-
Java
RootBeanDefinition beanDefinition = new RootBeanDefinition(ClientFactoryBean.class);
beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(ClientFactoryBean.class, MyClient.class));
// ...
registry.registerBeanDefinition("myClient", beanDefinition);
JPA
必须预先知道 JPA 持久性单元,才能应用某些优化。请考虑以下基本示例:
-
Java
@Bean
LocalContainerEntityManagerFactoryBean customDBEntityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("com.example.app");
return factoryBean;
}
为了确保提前进行扫描,必须声明一个 bean 并由
Factory bean 定义,如以下示例所示:PersistenceManagedTypes
-
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;
}
运行时提示
与常规 JVM 运行时相比,将应用程序作为本机映像运行需要更多信息。 例如,GraalVM 需要提前知道组件是否使用反射。 同样,除非明确指定,否则 Classpath 资源不会包含在本机映像中。 因此,如果应用程序需要加载资源,则必须从相应的 GraalVM 原生映像配置文件中引用该资源。
RuntimeHints
API 收集运行时对反射、资源加载、序列化和 JDK 代理的需求。
以下示例确保在运行时可以从本机映像中的 Classpath 加载:config/app.properties
-
Java
runtimeHints.resources().registerPattern("config/app.properties");
在 AOT 处理期间,会自动处理许多合同。
例如,检查方法的返回类型,如果 Spring 检测到该类型应该被序列化(通常为 JSON),则添加相关的反射提示。@Controller
对于核心容器无法推断的情况,您可以以编程方式注册此类提示。 还为常见使用案例提供了许多方便的注释。
@ImportRuntimeHints
RuntimeHintsRegistrar
实施允许您获取对 AOT 引擎管理的实例的回调。
此接口的实现可以在任何 Spring bean 或工厂方法上注册。 在构建时检测并调用实现。RuntimeHints
@ImportRuntimeHints
@Bean
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/*");
}
}
}
如果可能的话,应该尽可能靠近需要提示的组件使用。
这样,如果组件没有贡献到 ,也不会贡献 hint。@ImportRuntimeHints
BeanFactory
也可以通过添加一个 key 等于接口的完全限定名称的 entry in 来静态注册 implementation。META-INF/spring/aot.factories
RuntimeHintsRegistrar
@Reflective
@Reflective
提供了一种惯用的方式来标记对带注释的元素进行反射的需求。
例如,是元注释的,因为底层实现使用反射调用带注释的方法。@EventListener
@Reflective
默认情况下,仅考虑 Spring bean,并为带注释的元素注册调用提示。
这可以通过 annotation 指定自定义实现来调整。ReflectiveProcessor
@Reflective
库作者可以出于自己的目的重复使用此批注。
如果需要处理 Spring bean 以外的组件,则可以检测相关类型并用于处理它们。BeanFactoryInitializationAotProcessor
ReflectiveRuntimeHintsRegistrar
@RegisterReflectionForBinding
@RegisterReflectionForBinding
是一种专用化,它注册了序列化任意类型的需求。
一个典型的用例是使用容器无法推断的 DTO,例如在方法主体中使用 Web 客户端。@Reflective
@RegisterReflectionForBinding
可以应用于类级别的任何 Spring bean,但它也可以直接应用于方法、字段或构造函数,以更好地指示实际需要提示的位置。
以下示例注册序列化。Account
-
Java
@Component
public class OrderService {
@RegisterReflectionForBinding(Account.class)
public void process(Order order) {
// ...
}
}
测试运行时提示
Spring Core 还附带了一个实用程序,用于检查现有提示是否与特定用例匹配。
这可以在您自己的测试中用于验证 a 是否包含预期结果。
我们可以为 our 编写一个测试,并确保我们能够在运行时加载一个字典: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 跟踪代理运行应用程序的测试套件(或应用程序本身)来了解有关应用程序的全局运行时行为的更多信息。 此代理将在运行时记录所有需要 GraalVM 提示的相关调用,并将其作为 JSON 配置文件写出。
为了更有针对性的发现和测试,Spring Framework 提供了一个带有核心 AOT 测试实用程序的专用模块。
此模块包含 RuntimeHints 代理,这是一个 Java 代理,用于记录与运行时提示相关的所有方法调用,并帮助您断言给定实例涵盖所有记录的调用。
让我们考虑一个基础设施,我们想在 AOT 处理阶段测试我们为其贡献的提示。"org.springframework:spring-core-test"
RuntimeHints
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.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.springframework