共计 9199 个字符,预计需要花费 23 分钟才能阅读完成。
Spring 对 AOT 优化的反对意味着将哪些通常在运行时才产生的事件提前到编译期做,包含在构建时查看 ApplicationContext,反对决策和发现执行逻辑。这样做能够构建一个更间接的应用程序启动安顿,并次要基于类门路和环境来关注一组固定的个性。
反对这样的优化意味着须要对原 Spring 利用做如下的限度:
classpath
是固定的,并在在构建时就曾经全副指定了。-
bean 的定义在运行时不能扭转。
@Profile
, 特地是须要在构建时抉择特定于配置文件的配置- 影响 bean 存在的环境属性配置
@Conditional
仅能在构建时思考
- 带有 Supplier(包含
lambda
和办法援用)的 Bean 的定义不能被 AOT 转换。 @Bean
注解的办法的返回类型得是具体的类,而不能是接口了,以便容许正确的提醒推断。
当以上的限度都防止了,就能够在构建时执行 AOT 的解决并生成额定的资产。
通过 Spring AOT 解决过的利用,通过会生成如下资产:
- Java 源码
- 字节码
RuntimeHints
, 用于反射,资源定位,序列化和 Java 反射
在当前情况下,Spring AOT 专一于应用 GraalVM 将 Spring 的利用部署为原生的镜像,后续可能会反对更多的 JVM。
AOT 引擎介绍
用于解决 ApplicationContext 排列的 AOT 引擎的入口点是 ApplicationContextAotGenerator
. 它负责以下步骤, 其基于的参数GenericApplicationContext
示意要被优化的利用,和一个通用的上下文参数GenerationContext
.
- 刷新用于 AOT 解决的
ApplicationContext
。与传统的刷新不同,此版本只创立 bean 定义,而不是 bean 实例 - 调用可用的
BeanFactoryInitializationAotProcessor
的具体实现,并对GenerationContext
应用。例如,外围实现 迭代所有候选 bean definition,并生成必要的代码以复原 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 解决的刷新仅利用惯例刷新的子集。AOT 解决可按如下形式触发:
RuntimeHints hints = new RuntimeHints();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyApplication.class);
context.refreshForAotProcessing(hints);
// ...
context.close();
在 AOT 模式下,BeanFactoryPostProcessor
扩大点的实现和平时一样调用。包含 configuration
类的解析、import selector
和类扫描等。这些步骤确保 BeanRegistry
蕴含应用程序的相干 bean 定义. 如果 Bean definition 收到 conditions
(如 @Profile
) 的爱护,则在该阶段会被摈弃。因为此模式实际上不创立 Bean 的实例,除了与 AOT 相干的变体实现之外,BeanPostProcessor
将不会被调用。变体实现包含:
MergedBeanDefinitionPostProcessor
的实现,后处理 bean 定义以提取其余设置,如init
和destroy
办法SmartInstantiationAwareBeanPostProcessor
的实现,如果须要,确定更准确的 bean 类型,这确保创立运行时须要的任何代理类。
一旦该步骤实现,BeanFactory
就蕴含了利用运行所必须的 bean definition 汇合。它不触发 bean 实例化,但容许 AOT 引擎查看将在运行时创立的 bean。
Bean 工厂初始化 AOT 奉献
心愿参加此步骤的组件能够实现 BeanFactoryInitializationAotProcessor
接口。每个实现都能够依据 bean 工厂的状态返回 AOT 奉献。
AOT 奉献是奉献生成的代码能够再现特定行为的组件。它还能够提供 RuntimeHints
来批示反射、资源加载、序列化或 JDK 代理的须要.
BeanFactoryInitializationAotProcessor
的实现能够注册在 META-INF/spring/aot.factories
中,key 为该接口的全限定名。
BeanFactoryInitializationAotProcessor
也能够间接被一个 bean 实现。在这种模式下,bean 提供的 AOT 奉献与它在惯例运行时提供的个性相当。因而,这样的 bean 会主动从 AOT 优化上下文中排除。
留神: 如果 bean 实现了 BeanFactoryInitializationAotProcessor
接口,那么在 AOT 解决期间将初始化 bean 及其所有依赖项。咱们通常倡议此接口仅由根底构造 bean(如 BeanFactoryPostProcessor
)实现,这些 bean 具备无限的依赖性,并且在 bean 工厂生命周期的晚期就曾经初始化。如果这样的 bean 是应用@bean
工厂办法注册的,请确保该办法是动态的 ,以便其关闭的@Configuration
类不用初始化。
Bean 注册 AOT 奉献
BeanFactoryInitializationAotProcessor
实现的外围性能是负责为每个候选 BeanDefinition 收集必要的奉献。它应用专用的 BeanRegistryAotProcessor
来实现。
该接口的应用形式如下:
- 由
BeanPostProcessor
bean 实现,以替换其运行时行为。例如,AutowiredAnnotationBeanPostProcessor 实现了这个接口,以生成注入用 @Autowired 正文的成员的代码。 - 由
META-INF/spring/aot.factors
中注册的类型实现,其 key 等于接口的齐全限定名称。通常在须要针对外围框架的特定个性进行调整的 bean 定义时应用。
留神: 如果一个 bean 实现了 BeanRegistryAotProcessor
接口,那么在 AOT 解决期间将初始化该 bean 及其所有依赖项。咱们通常倡议此接口仅由根底构造 bean(如 BeanFactoryPostProcessor
)实现,这些 bean 具备无限的依赖性,并且在 bean 工厂生命周期的晚期就曾经初始化。如果这样的 bean 是应用@bean
工厂办法注册的,请确保该办法是动态的 ,以便其关闭的@Configuration
类不用初始化。
如果没有 BeanRegisterationAotProcessor 解决特定注册的 bean,则默认实现会解决它。这是默认行为,因为为 bean definition 调整生成的代码应该仅限于比拟冷门的应用案例。
以后面的示例为例,咱们假如 DataSourceConfiguration 如下:
@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
@Bean
public SimpleDataSource dataSource() {return new SimpleDataSource();
}
}
因为该类上没有任何特定条件,因而 dataSourceConfiguration
和dataSource
被标识为候选项。AOT 引擎会将下面的配置类转换为与以下相似的代码:
/**
* Bean definitions for {@link DataSourceConfiguration}
*/
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 定义的确切性质,生成的确切代码可能有所不同。
下面生成的代码创立了与 @Configuration
类等效的 bean 定义,但以间接的形式,如果可能的话,不应用反射。dataSourceConfiguration 有一个 bean 定义,dataSourceBean 有一个。当须要数据源实例时,将调用 BeanInstance Supplier。此 Supplier 调用 dataSourceConfiguration bean 上的 dataSource()办法。
运行时提醒(Runtime Hints)
与惯例 JVM 运行时相比,将应用程序作为 native image 运行须要额定的信息。例如,GraalVM 须要提前晓得组件是否应用反射。相似地,除非明确指定,否则类门路资源不会在 native image 中提供。因而,如果应用程序须要加载资源,则必须从相应的 GraalVM native image 配置文件中援用该资源。
RuntimeHints
的 API 收集运行时对反射、资源加载、序列化和 JDK 代理的需要。以下示例确保 config/app.properties
能够在运行时从本机映像中的类门路加载。
runtimeHints.resources().registerPattern("config/app.properties");
在 AOT 处理过程中,会主动解决许多合同。例如:查看 @Controller
办法的返回类型,如果 Spring 检测到类型应该序列化(通常为 JSON),则增加相干的反射提醒。
对于外围容器无奈推断的状况,能够以编程形式注册此类提醒。还为常见用例提供了许多不便的正文。
@ImportRuntimeHints
RuntimeHintsRegister
实现容许您获取对 AOT 引擎治理的 RuntimeHints
实例的回调。能够在任何 Spring 的 bean 实例或 @bean
工厂办法上应用 @ImportRuntimeHints
注册此接口的实现。在构建时检测并调用 RuntimeHintsRegister
实现。
@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
,hints
也不会被奉献。
@Reflective
@Reflective
提供了一种习用的办法来标记对带注解元素的反射的须要。例如,@EventListener
应用 @Reflective
进行元正文,因为底层实现应用反射调用正文办法.
默认状况下,只思考 Spring 的 bean,并为带注解的元素注册调用提醒。这能够通过 @Reflective
注解指定自定义 ReflectiveProcessor
实现来调整。
库作者能够出于本人的目标重用此正文。如果须要解决 Spring bean 以外的组件,BeanFactoryInitializationAotProcessor
能够检测相干类型并应用 ReflectiveRuntimeHintsRegister
来解决它们。
@RegisterReflectionForBinding
@RegisterReflectionForBinding
是 @Reflective
的特例,它注册了序列化任意类型的须要。典型的用例是容器无奈推断的 DTO 的应用,例如在办法体中应用 web 客户端。
@RegisterReflectionForBinding
能够利用于类级别的任何 Spring bean,但也能够间接利用于办法、字段或构造函数,以更好地批示理论须要提醒的地位。以下示例 注册 Account 以进行序列化。
@Component
public class OrderService {@RegisterReflectionForBinding(Account.class)
public void process(Order order) {// ...}
}
测试 Runtime Hints
Spring Core 还提供 RuntimeHintsPredices
,这是一个用于查看现有提醒是否匹配特定用例的实用程序。这能够在您本人的测试中应用,以验证RuntimeHintsRegister
是否蕴含预期后果。咱们能够为咱们的 SpellCheckService 编写测试,并确保咱们可能在运行时加载字典:
@Test
void shouldRegisterResourceHints() {RuntimeHints hints = new RuntimeHints();
new SpellCheckServiceRuntimeHints().registerHints(hints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt"))
.accepts(hints);
}
应用RuntimeHintsPredices
,咱们能够查看反射、资源、序列化或代理生成提醒。这种办法实用于单元测试,但意味着组件的运行时行为是家喻户晓的。通过应用 GraalVM 跟踪代理运行应用程序的测试套件(或应用程序自身),能够理解无关应用程序全局运行时行为的更多信息。该代理将在运行时记录所有须要 GraalVM 提醒的相干调用,并将其作为 JSON 配置文件写入。
为了更具针对性的发现和测试,Spring Framework 提供了一个带有外围 AOT 测试实用程序的专用模块,“org.springframework:Spring-core 测试”。此模块蕴含 RuntimeHints Agent,这是一个 Java 代理,它记录与运行时提醒相干的所有办法调用,并帮忙您断言给定的 RuntimeHinds 实例笼罩所有记录的调用。让咱们思考一个基础设施,咱们心愿测试在 AOT 解决阶段提供的提醒。
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);
}
}
}
而后,咱们能够编写一个单元测试(不须要本机编译),查看咱们提供的提醒:
@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
该文章的内容来自于 Spring 官网手册,原文内容:https://docs.spring.io/spring…