当前位置: 首页 > 后端技术 > Java

SpringAOT介绍

时间:2023-04-02 10:05:04 Java

Spring支持AOT优化,意味着通常在运行时发生的事情会提前在编译时完成,包括在构建时检查ApplicationContext,支持决策和发现执行逻辑。这样做可以构建更直接的应用程序启动安排,并专注于主要基于类路径和环境的一组固定功能。支持这样的优化意味着原来的Spring应用需要做如下限制:类路径是固定的,并且在构建时已经完全指定。bean定义不能在运行时更改。@Profile,特别需要在构建时选择profile-specific的配置,影响bean存在的环境属性配置.用@Bean注释的方法的返回类型必须是具体类,而不是接口,以便允许正确的提示推断。当避免上述限制时,可以执行AOT处理并在构建时生成额外的资产。SpringAOT处理的应用会生成如下资产:Java源码字节码RuntimeHints,用于反射、资源定位、序列化和Java反射目前来看,SpringAOT侧重于使用GraalVM部署Spring应用,作为原生镜像可能支持更多未来的JVM。AOT引擎介绍AOT引擎处理ApplicationContext排列的入口点是ApplicationContextAotGenerator。它负责以下步骤。它的基础参数GenericApplicationContext表示要优化的应用程序,还有一个通用上下文参数GenerationContext。刷新AOT处理的ApplicationContext。与传统的刷新不同,这个版本只创建bean定义,而不是bean实例,调用在GenerationContext上可用和使用的BeanFactoryInitializationAotProcessor的具体实现。例如,核心实现迭代所有候选bean定义并生成必要的代码来恢复BeanFactory的状态。处理完成后,GenerationContext将更新为生成的代码、资源和应用程序运行所需的类。RuntimeHints实例可用于生成与GraalVM相关的本机图像配置文件。ApplicationContextAotGenerator#processAheadOfTime返回允许AOT优化的启动上下文的ApplicationContextInitializer入口点的类名。刷新AOT处理所有GenericApplicationContext实现都支持AOT处理刷新。应用程序上下文是从任意数量的入口点创建的,通常以@Configuration注释类的形式。一个典型的实现如下:@Configuration(proxyBeanMethods=false)@ComponentScan@Import({DataSourceConfiguration.class,ContainerConfiguration.class})publicclassMyApplication{}使用常规运行时启动这个应用程序涉及许多步骤,包括类路径扫描、配置类解析、bean实例化和生命周期回调处理。AOT处理的刷新仅应用常规刷新的一个子集。可以通过如下方式触发AOT处理:RuntimeHintshints=newRuntimeHints();AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext();context.register(MyApplication.class);context.refreshForAotProcessing(hints);//...context.close();在AOT模式下,BeanFactoryPostProcessor扩展点的实现照常被调用。包括配置类分析、导入选择器和类扫描等。这些步骤确保BeanRegistry包含应用程序的相关bean定义。如果Bean定义被条件保护(比如@Profile),那么它会在这个阶段被丢弃。因为这种模式实际上并没有创建Bean的实例,所以除了AOT相关的变体实现之外,不会调用BeanPostProcessor。变体实现包括:MergedBeanDefinitionPostProcessor的实现,它对bean定义进行后处理以提取其他设置,例如init和destroy方法SmartInstantiationAwareBeanPostProcessor的实现,它在需要时确定更精确的bean类型,这确保创建运行时所需的任何代理类.完成此步骤后,BeanFactory将包含应用程序运行所需的一组bean定义。它不会触发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接口,那么这个bean及其所有依赖项将在AOT处理期间被初始化。我们通常建议此接口仅由具有有限依赖性并在bean工厂生命周期早期初始化的基础结构bean(例如BeanFactoryPostProcessor)实现。如果这样的bean是使用@bean工厂方法注册的,请确保该方法是静态的,以便不必初始化其封闭的@Configuration类。BeanRegistrationAOTContributionBeanFactoryInitializationAotProcessor实现的核心功能负责为每个候选BeanDefinition收集必要的贡献。它使用专用的BeanRegistryAotProcessor来实现。此接口的使用如下:由BeanPostProcessorbean实现以替换其运行时行为。例如,AutowiredAnnotationBeanPostProcessor实现此接口以生成用于注入使用@Autowired注释的成员的代码。由在META-INF/spring/aot.factors中注册的类型实现,其键等于接口的完全限定名称。通常在需要针对核心框架的特定功能调整的bean定义时使用。注意:如果一个bean实现了BeanRegistryAotProcessor接口,则该bean及其所有依赖项将在AOT处理期间被初始化。我们通常建议此接口仅由具有有限依赖性并在bean工厂生命周期早期初始化的基础结构bean(例如BeanFactoryPostProcessor)实现。如果这样的bean是使用@bean工厂方法注册的,请确保该方法是静态的,以便不必初始化其封闭的@Configuration类。如果没有BeanRegisterationAotProcessor处理特定的已注册bean,则默认实现会处理它。这是默认行为,因为为bean定义调优生成的代码应该仅限于不太流行的用例。以前面的例子为例,我们假设DataSourceConfiguration是这样的:由于此类没有特定条件,因此将dataSourceConfiguration和dataSource确定为候选者。AOT引引擎将上面的配置类转换为与下面类的代码:/***{@linkDataSourceConfiguration}的Bean定义*/publicclassDataSourceConfiguration__BeanDefinitions{/***获取'dataSourceConfiguration'的bean定义*/publicstaticBeanDefinitiongetDataSourceConfigurationBeanDefinition(){ClassbeanType=DataSourceConfiguration.class;RootBeanDefinitionbeanDefinition=newRootBeanDefinition(beanType);beanDefinition.setInstanceSupplier(DataSourceConfiguration::new);返回bean定义;}/***获取“dataSource”的bean实例供应商。*/privatestaticBeanInstanceSuppliergetDataSourceInstanceSupplier(){returnBeanInstanceSupplier.forFactoryMethod(DataSourceConfiguration.class,"dataSource").withGenerator((registeredBean)->registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource());}/***获取'dataSource'的bean定义*/publicstaticBeanDefinitiongetDataSourceBeanDefinition(){ClassbeanType=SimpleDataSource.class;RootBeanDefinitionbeanDefinition=newRootBeanDefinition(beanType);beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier());返回bean定义;}}根据bean定义的确切性质,生成的确切代码可能会有所不同。尽可能不要使用反射。dataSourceConfiguration有一个bean定义,dataSourceBean也有一个。BeanInstanceSupplier在需要数据源实例时被调用。该供应商调用dataSourceConfigurationbean上的dataSource()方法。运行时提示(RuntimeHints)与常规JVM运行时相比,将应用程序作为原生映像运行需要额外的信息。例如,GraalVM需要提前知道一个组件是否使用了反射。同样,除非明确指定,否则不会在本机图像中提供类路径资源。因此,如果应用程序需要加载资源,则必须从对应的GraalVM原生镜像配置文件中引用。RuntimeHintsAPI收集反射、资源加载、序列化和JDK代理的运行时要求。以下示例确保可以在运行时从本机映像中的类路径加载config/app.properties。runtimeHints.resources().registerPattern("config/app.properties");在AOT处理过程中,很多合约都是自动处理的。例如:检查@Controller方法的返回类型,如果Spring检测到该类型应该被序列化(通常是JSON),则添加相关的反射提示。对于核心容器无法推断的情况,可以通过编程方式注册此类提示。对于常见用例,还有许多方便的注释。@ImportRuntimeHintsRuntimeHintsRegister实现允许您获得对AOT引擎管理的RuntimeHints实例的回调。可以在任何Springbean实例或@bean工厂方法上使用@ImportRuntimeHints注册此接口的实现。在构建时检测并调用RuntimeHintsRegister实现。@Component@ImportRuntimeHints(SpellCheckService.SpellCheckServiceRuntimeHints.class)publicclassSpellCheckService{publicvoidloadDictionary(Localelocale){ClassPathResourceresource=newClassPathResource("dicts/"+locale.getLanguage()+".txt");//...}staticclassSpellCheckServiceRuntimeHintsimplementsRuntimeHintsRegistrar{@OverridepublicvoidregisterHints(RuntimeHintshints,ClassLoaderclassLoader){hints.resources().registerPattern("dicts/*");}}}}@ImportRuntimeHints应放置在尽可能靠近所需提示的位置,尽可能使用组件。这样,如果组件没有贡献给BeanFactory,那么提示也不会被贡献。@Reflective@Reflective提供了一种惯用的方式来标记需要对带注释的元素进行反射。例如,@EventListener使用@Reflective进行元注解,因为底层实现使用反射调用注解的方法。默认情况下,只考虑Spring的bean,并为带注释的元素注册调用提示。这可以通过@Reflective注释指定自定义ReflectiveProcessor实现来调整。库作者可以出于自己的目的重用此注释。如果需要处理Springbean之外的组件,BeanFactoryInitializationAotProcessor可以检测相关类型,使用ReflectiveRuntimeHintsRegister来处理。@RegisterReflectionForBinding@RegisterReflectionForBinding是@Reflective的一个特例,它注册了序列化任意类型的需要。一个典型的用例是使用容器无法推断的DTO,例如在方法主体中使用Web客户端。@RegisterReflectionForBinding可以应用于类级别的任何Springbean,但也可以直接应用于方法、字段或构造函数,以更好地指示实际需要提示的位置。下面的示例注册一个用于序列化的帐户。@ComponentpublicclassOrderService{@RegisterReflectionForBinding(Account.class)publicvoidprocess(Orderorder){//...}}测试运行时提示SpringCore还提供了RuntimeHintsPredices,一种用于检查现有提示是否与特定用例程序匹配的实用程序。这可用于您自己的测试,以验证RuntimeHintsRegister是否包含预期结果。我们可以为我们的SpellCheckService编写测试,并确保我们能够在运行时加载字典:@TestvoidshouldRegisterResourceHints(){RuntimeHintshints=newRuntimeHints();newSpellCheckServiceRuntimeHints().registerHints(hints,getClass().getClassLoader());assertThat(RuntimeHintsPredicates.resource().forResource("dicts/en.txt")).accepts(hints);}使用RuntimeHintsPredicates我们可以检查反射、资源、序列化或代理生成提示。这种方法适用于单元测试,但意味着组件的运行时行为是众所周知的。通过使用GraalVM跟踪代理运行应用程序的测试套件(或应用程序本身),您可以了解有关应用程序全局运行时行为的更多信息。代理将在运行时记录所有需要GraalVM提示的相关调用,并将它们写入JSON配置文件。为了更有针对性的发现和测试,SpringFramework提供了一个带有核心AOT测试实用程序的专用模块,“org.springframework:spring-core-test”。此模块包含RuntimeHints代理,这是一个Java代理,它记录与运行时提示相关的所有方法调用,并帮助您断言给定的RuntimeHints实例涵盖所有记录的调用。让我们考虑一个基础设施,我们希望在其中测试AOT处理阶段提供的提示。publicclassSampleReflection{privatefinalLoglogger=LogFactory.getLog(SampleReflection.class);publicvoidperformReflection(){try{ClassspringVersion=ClassUtils.forName("org.springframework.core.SpringVersion",null);方法getVersion=ClassUtils.getMethod(springVersion,"getVersion");字符串版本=(字符串)getVersion.invoke(null);logger.info("春季版:"+version);}catch(Exceptionexc){logger.error("反射失败",exc);然后我们可以编写一个单元测试(不需要本机编译)来检查我们提供的提示:@EnabledIfRuntimeHintsAgentclassSampleReflectionRuntimeHintsTests{@TestvoidshouldRegisterReflectionHints(){//调用提供提示的RuntimeHintsRegistrar,例如:runtimeHints.reflection().registerType(SpringVersion.class,typeHint->typeHint.withMethod("getVersion",List.of(),ExecutableMode.INVOKE));//在记录lambda中调用我们要测试的相关代码RuntimeHintsInvocationsinvocations=RuntimeHintsRecorder.record(()->{SampleReflectionsample=newSampleReflection();sample.performReflection();});//断言记录的调用被贡献的提示覆盖assertThat(invocations).match(runtimeHints);如果您忘记提供提示,测试将失败并显示有关调用的一些详细信息:org.springframework.docs.core.aot.hints.testing.SampleReflectionperformReflectionINFO:Springversion:6.0.0-SNAPSHOTMissing<"ReflectionHints">用于调用参数["org.springframework.core.SpringVersion",false,jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7].Stacktrace:<"org.springframework.util.ClassUtils#forName,Line284io.spring.runtimehintstesting.SampleReflection#performReflection,Line19io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0,Line25本文内容来自Spring官方手册,原文内容:https://文档.spring。IO/弹簧...

最新推荐
猜你喜欢