Spring对AOT优化的反对意味着将哪些通常在运行时才产生的事件提前到编译期做,包含在构建时查看ApplicationContext,反对决策和发现执行逻辑。这样做能够构建一个更间接的应用程序启动安顿,并次要基于类门路和环境来关注一组固定的个性。

反对这样的优化意味着须要对原Spring利用做如下的限度:

  1. classpath是固定的,并在在构建时就曾经全副指定了。
  2. bean的定义在运行时不能扭转。

    1. @Profile,特地是须要在构建时抉择特定于配置文件的配置
    2. 影响bean存在的环境属性配置@Conditional仅能在构建时思考
  3. 带有Supplier(包含lambda和办法援用)的Bean的定义不能被AOT转换。
  4. @Bean注解的办法的返回类型得是具体的类,而不能是接口了,以便容许正确的提醒推断。

当以上的限度都防止了,就能够在构建时执行AOT的解决并生成额定的资产。

通过Spring AOT解决过的利用,通过会生成如下资产:

  1. Java源码
  2. 字节码
  3. RuntimeHints,用于反射,资源定位,序列化和Java反射

在当前情况下,Spring AOT专一于应用GraalVM将Spring的利用部署为原生的镜像,后续可能会反对更多的JVM。

AOT引擎介绍

用于解决ApplicationContext排列的AOT引擎的入口点是ApplicationContextAotGenerator.它负责以下步骤,其基于的参数GenericApplicationContext示意要被优化的利用,和一个通用的上下文参数GenerationContext.

  1. 刷新用于AOT解决的ApplicationContext。与传统的刷新不同,此版本只创立bean定义,而不是bean实例
  2. 调用可用的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将不会被调用。变体实现包含:

  1. MergedBeanDefinitionPostProcessor的实现,后处理bean定义以提取其余设置,如initdestroy办法
  2. 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来实现。

该接口的应用形式如下:

  1. BeanPostProcessorbean实现,以替换其运行时行为。例如,AutowiredAnnotationBeanPostProcessor实现了这个接口,以生成注入用@Autowired正文的成员的代码。
  2. 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();    }}

因为该类上没有任何特定条件,因而dataSourceConfigurationdataSource被标识为候选项。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应尽可能凑近须要提醒的组件应用。这样,如果组件没有被奉献给BeanFactoryhints也不会被奉献。

@Reflective

@Reflective提供了一种习用的办法来标记对带注解元素的反射的须要。例如,@EventListener应用@Reflective进行元正文,因为底层实现应用反射调用正文办法.

默认状况下,只思考Spring的bean,并为带注解的元素注册调用提醒。这能够通过@Reflective注解指定自定义ReflectiveProcessor实现来调整。

库作者能够出于本人的目标重用此正文。如果须要解决Spring bean以外的组件,BeanFactoryInitializationAotProcessor能够检测相干类型并应用ReflectiveRuntimeHintsRegister来解决它们。

@RegisterReflectionForBinding

@RegisterReflectionForBinding@Reflective的特例,它注册了序列化任意类型的须要。典型的用例是容器无奈推断的DTO的应用,例如在办法体中应用web客户端。

@RegisterReflectionForBinding能够利用于类级别的任何Spring bean,但也能够间接利用于办法、字段或构造函数,以更好地批示理论须要提醒的地位。以下示例 注册Account以进行序列化。

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

测试 Runtime Hints

Spring Core还提供RuntimeHintsPredices,这是一个用于查看现有提醒是否匹配特定用例的实用程序。这能够在您本人的测试中应用,以验证RuntimeHintsRegister是否蕴含预期后果。咱们能够为咱们的SpellCheckService编写测试,并确保咱们可能在运行时加载字典:

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

而后,咱们能够编写一个单元测试(不须要本机编译),查看咱们提供的提醒:

@EnabledIfRuntimeHintsAgentclass 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 performReflectionINFO: Spring version:6.0.0-SNAPSHOTMissing <"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 284io.spring.runtimehintstesting.SampleReflection#performReflection, Line 19io.spring.runtimehintstesting.SampleReflectionRuntimeHintsTests#lambda$shouldRegisterReflectionHints$0, Line 25

该文章的内容来自于Spring官网手册,原文内容:https://docs.spring.io/spring...