Spring6和SpringBoot3的新特性-你不得不了解的AOT原来这么简单

article/2025/10/4 2:08:18

Spring6.0新特性

一、Spring的发展历史

image.png

二、AOT

  AOT是Spring6.0提供的一个新特性,Ahead of Time 提前编译。

image.png

1.AOT概述

1.1 JIT和AOT的关系

1.1.1 JIT

   JIT(Just-in-time) 动态编译,即时编译,也就是边运行边编译,也就是在程序运行时,动态生成代码,启动比较慢,编译时需要占用运行时的资源。

1.1.2 AOT

  AOT,Ahead Of Time 指的是运行前编译,预先编译,AOT 编译能直接将源代码转化为机器码,内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化,AOT 缺点就是在程序运行前编译会使程序安装的时间增加。

简单来讲:JIT即时编译的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。

image.png

三、GraalVM

GraalVM即支持AOT也支持JIT。支持多种开发语言。

  Spring6 支持的 AOT 技术,这个 GraalVM 就是底层的支持,Spring 也对 GraalVM 本机映像提供了一流的支持。GraalVM 是一种高性能 JDK,旨在加速用 Java 和其他 JVM 语言编写的应用程序的执行,同时还为 JavaScript、Python 和许多其他流行语言提供运行时。 GraalVM 提供两种运行 Java 应用程序的方法:在 HotSpot JVM 上使用 Graal 即时 (JIT) 编译器或作为提前 (AOT) 编译的本机可执行文件。 GraalVM 的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外语调用成本。GraalVM 向 HotSpot Java 虚拟机添加了一个用 Java 编写的高级即时 (JIT) 优化编译器。

GraalVM 具有以下特性:

(1)一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源

(2)AOT 本机图像编译提前将 Java 应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能

(3)Polyglot 编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销

(4)高级工具在 Java 和多种语言中调试、监视、分析和优化资源消耗

1.GraalVM安装

1.1 下载GraalVM

下载地址:https://www.graalvm.org/downloads/image.png

下载社区版本即可,点击进入选择相关的版本:https://github.com/graalvm/graalvm-ce-builds/releases

image.png

下载好后解压缩出来

image.png

1.2 配置环境变量

添加:GRAALVM_HOME

编辑用户变量

image.png

把JAVA_HOME修改为graalvm的位置

image.png

检查是否配置成功

image.png

1.3 安装native-image插件

使用命令 gu install native-image 下载安装插件,因为社区版默认不提供支持。需要手动下载

image.png

image.png

1.4 Native Image

  Native image(本地镜像)是一种在Java平台上构建本地应用程序的技术。它将Java应用程序编译成本地机器代码,以便在不需要Java虚拟机(JVM)的情况下运行。这使得应用程序可以更快地启动,更高效地执行,并且占用更少的内存。

  Native image使用GraalVM编译器技术,可以将Java应用程序转换为本地可执行文件,支持Windows、Linux和MacOS等多个操作系统平台。此外,Native image还可以将Java应用程序打包成单个可执行文件,从而方便部署和分发。

  使用Native image,开发人员可以将Java应用程序作为本地应用程序来构建和部署,从而获得更好的性能和更好的用户体验。

2.安装C++的编译环境

2.1 下载Visual Studio

https://visualstudio.microsoft.com/zh-hans/downloads/

image.png

同样我们下载社区版本即可

2.2 安装Visual Studio

下载后双击直接安装即可

image.png

等待在线下载

image.png

image.png

image.png

注意安装选项,然后继续等待

image.png

创建一个普通Hello.java文件

public class Hello{public static void main(String[] args){System.out.println("Hello World ...");}
}

然后通过 javac Hello.java 编译

image.png

通过native-image Hello 执行

image.png

通过 native-image 生成了 Hello.exe 文件,我们就可以直接生成了。

image.png

四、SpringBoot实战

  我们同样可以在SpringBoot项目中通过AOT来提前编译我们的项目,新建一个Maven项目。然后添加相关的依赖

    <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.2</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>

同时我们还需要添加相关的SpringBoot插件

    <build><plugins><plugin><groupId>org.graalvm.buildtools</groupId><artifactId>native-maven-plugin</artifactId></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>

然后我们编写一点简单的代码测试即可

image.png

然后我们打开 x64 Native Tools Command Prompt for VS 2019 。然后我们需要切换到工程目录下

image.png

然后执行 mvn -Pnative native:compile 进行编译就可以了,编译成功就会在target目录下生成 EXE 文件。后续执行该文件就可以了

image.png

image.png

编译成功

image.png

然后我们双击执行exe文件即可。你会发现速度会快很多

image.png

五、RuntimeHints

  与常规 JVM 运行时相比,将应用程序作为本机映像运行需要额外的信息。例如,GraalVM 需要提前知道组件是否使用反射。同样,除非明确指定,否则类路径资源不会在本机映像中提供。因此,如果应用程序需要加载资源,则必须从相应的 GraalVM 原生图像配置文件中引用它。

APIRuntimeHints在运行时收集反射、资源加载、序列化和 JDK 代理的需求。

1.案例分析

声明个普通的实体类型

public class UserEntity {public String hello(){return "hello ...";}
}

然后我们在控制器中通过反射来操作处理

    @GetMapping("/hello")public String hello(){String res = "hello";try {Method hello = UserEntity.class.getMethod("hello");res =  (String)hello.invoke(UserEntity.class.newInstance(),null);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}return res;}

然后通过命令编译为 exe 文件

image.png

运行exe文件后。我们通过浏览器发起请求

image.png

在HelloController中。我们通过反射的方式使用到了UserEntity的无参构造方法。如果不做任何处理。那么打成二进制可执行文件后是执行不了的。上面是具体的报错信息。针对这种情况。我们可以通过 Runtime Hints 机制来处理。

2. RuntimeHintsRegistrar

官网提供的解决方案。我们自定义一个RuntimeHintsRegistrar接口的实现类,然后把该实现类注入到Spring中

image.png

我们自己的实现

@RestController
@ImportRuntimeHints(HelloController.UserEntityRuntimeHints.class)
public class HelloController {@GetMapping("/hello")public String hello(){String res = "hello";try {Method hello = UserEntity.class.getMethod("hello");res =  (String)hello.invoke(UserEntity.class.newInstance(),null);} catch (NoSuchMethodException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);}return res;}static class UserEntityRuntimeHints implements RuntimeHintsRegistrar{@Overridepublic void registerHints(RuntimeHints hints, ClassLoader classLoader) {try {hints.reflection().registerConstructor(UserEntity.class.getConstructor(), ExecutableMode.INVOKE);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}
}

六、SpringBoot的核心代码

SpringApplicationAotProcessor

  我们执行 mvn -Pnative native:compile时会执行GraalVM中的相关指令。最终会调用SpringApplicationAotProcessor中的main 方法来完成相关提前编译操作。

	public static void main(String[] args) throws Exception {int requiredArgs = 6; // 调用main方法接收的有6个参数Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + SpringApplicationAotProcessor.class.getName()+ " <applicationName> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId> <originalArgs...>");// 获取SpringBoot项目的入口classClass<?> application = Class.forName(args[0]);// 通过传递过来的参数完成相关生成目录的配置Settings settings = Settings.builder().sourceOutput(Paths.get(args[1])).resourceOutput(Paths.get(args[2])).classOutput(Paths.get(args[3])).groupId((StringUtils.hasText(args[4])) ? args[4] : "unspecified").artifactId(args[5]).build();String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length): new String[0];// 执行 process 方法new SpringApplicationAotProcessor(application, settings, applicationArgs).process();}

进入到process 方法中

	public final T process() {try {// 设置状态System.setProperty(AOT_PROCESSING, "true");return doProcess(); // 处理的核心方法}finally {System.clearProperty(AOT_PROCESSING);}}

进入到 doProcess() 方法中

 	@Overrideprotected ClassName doProcess() {deleteExistingOutput(); // 删除已经存在的目录// 启动SpringBoot服务  但是不会做扫描beanGenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());return performAotProcessing(applicationContext);}

prepareApplicationContext方法

	@Overrideprotected GenericApplicationContext prepareApplicationContext(Class<?> application) {return new AotProcessorHook(application).run(() -> {Method mainMethod = application.getMethod("main", String[].class);return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs });});}

此次会执行启动类中的main方法来启动SpringBoot,

image.png

在启动中创建Spring上下文对象时会做如下的处理

	private ConfigurableApplicationContext createContext() {if (!AotDetector.useGeneratedArtifacts()) {return new AnnotationConfigServletWebServerApplicationContext();}return new ServletWebServerApplicationContext();}

  如果没有使用AOT,那么就会创建AnnotationConfigServletWebServerApplicationContext,它里面会添加ConfigurationClassPostProcessor,从而会解析配置类,从而会扫描,而如果使用了AOT,则会创建  ServletWebServerApplicationContext,它就是一个空容器,它里面没有ConfigurationClassPostProcessor,所以后续不会触发扫描了

再回到performAotProcessing方法中

	protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();DefaultGenerationContext generationContext = new DefaultGenerationContext(createClassNameGenerator(), generatedFiles);ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();// 进行相关的扫描操作ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext);//如果有反射的注册信息。这里会完成相关信息的生成到reflect-config.json对应的RuntimeHints中去registerEntryPointHint(generationContext, generatedInitializerClassName);// 生成source目录下的Java文件generationContext.writeGeneratedContent();//  将RuntimeHints中的内容写入resource目录下的Graalvm的各个配置文件中writeHints(generationContext.getRuntimeHints());writeNativeImageProperties(getDefaultNativeImageArguments(getApplicationClass().getName()));return generatedInitializerClassName;}

processAheadOfTime中的逻辑

	public ClassName processAheadOfTime(GenericApplicationContext applicationContext,GenerationContext generationContext) {return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {// 扫描处理applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());// 获取bean工厂对象DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();ApplicationContextInitializationCodeGenerator codeGenerator =new ApplicationContextInitializationCodeGenerator(generationContext);new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext, codeGenerator);return codeGenerator.getGeneratedClass().getName();});}

进入到refreshForAotProcessing方法中

	public void refreshForAotProcessing(RuntimeHints runtimeHints) {if (logger.isDebugEnabled()) {logger.debug("Preparing bean factory for AOT processing");}prepareRefresh();obtainFreshBeanFactory(); // 获取工厂对象。并完成扫描操作prepareBeanFactory(this.beanFactory);postProcessBeanFactory(this.beanFactory);invokeBeanFactoryPostProcessors(this.beanFactory); // 之后工厂的后置处理器this.beanFactory.freezeConfiguration();PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);preDetermineBeanTypes(runtimeHints);}

BeanFactoryInitializationAotContributions方法的逻辑:会读取aot.properties文件的加载器以及BeanFactory封装成为一个Loader对象,然后传入

	BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory) {this(beanFactory, AotServices.factoriesAndBeans(beanFactory));}

image.png


http://chatgpt.dhexx.cn/article/vxXCpaKU.shtml

相关文章

JIT VS AOT

一、AOT&#xff0c;JIT是什么 JIT&#xff0c;即Just-in-time,动态(即时)编译&#xff0c;边运行边编译&#xff1b;AOT&#xff0c;Ahead Of Time&#xff0c;指运行前编译&#xff0c;是两种程序的编译方式 理解 jit、aot 程序主要有两种运行方式&#xff1a;静态编译与动…

对比JIT和AOT,各自的优点与缺点

编译器和解释器是什么 编译器和解释器的区别在于是否编译和执行过程是否是同时进行。 编译器所干的事&#xff0c;将一门语言 X 编译为另一门语言 Y &#xff08;可以是语言 X、高级语言、低级语言等&#xff09;&#xff0c;整个编译过程涉及词法分析、语法分析、语义分析。…

AOT(超前编译)实例分析

文章目录 一、背景二、具体实施2.1 tfcomfile 是什么&#xff1f;2.2 tfcompile 的功能是什么&#xff1f;2.3使用tfcompile 三、总结 一、背景 前边已经了解了JIT和AOT的基本概念&#xff0c;AOT(提前编译)方式就是在代码执行阶段之前全部编译成目标指令&#xff0c;进入执行…

Java在云原生的破局利器——AOT(JIT与AOT)

导读 JIT&#xff08;Just-in-Time&#xff0c;实时编译&#xff09;一直是Java语言的灵魂特性之一&#xff0c;与之相对的AOT&#xff08;Ahead-of-Time&#xff0c;预编译&#xff09;方式&#xff0c;似乎长久以来和Java语言都没有什么太大的关系。但是近年来随着Serverles…

AOT概述

11.1、AOT概述 11.1.1、JIT与AOT的区别 JIT和AOT 这个名词是指两种不同的编译方式&#xff0c;这两种编译方式的主要区别在于是否在“运行时”进行编译 &#xff08;1&#xff09;JIT&#xff0c; Just-in-time,动态(即时)编译&#xff0c;边运行边编译&#xff1b; 在程序…

Python interpreter state is not initialized. The process may be terminated.

Error occurred when finalizing GeneratorDataset iterator: Failed precondition: Python interpreter state is not initialized. The process may be terminated. [[{{node PyFunc}}]] 当我在使用TensorFlow训练时&#xff0c;出现以上错误。 我也曾以为是&#xff…

subprocess.CalledProcessError: Command ‘[where, cl]‘ returned non-zero exit status 1

当运行程序时&#xff0c;程序报错最后一行错误输出为 subprocess.CalledProcessError: Command [where, c1] returned non-zero exit status 1时&#xff0c;我们把输出得错误往前翻看&#xff0c;如果&#xff0c;报错得第一行 是UserWarning: Error checking compiler vers…

Problem Solving Process of The terminal process terminated with exit code 1

参考前辈的配置VScode C/C环境的经验&#xff1a; 成成赐我力量 bat67 参考之后我的配置 c_cpp_properties.json "configurations": [{"name": "Mac","includePath": ["/usr/include"],"browse" : {"limitS…

compilation terminated.

wqlubuntu:~/Linux/COURSE$ gcc -o fork.c test.c gcc: error: test.c: No such file or directory gcc: fatal error: no input files compilation terminated.今天在编译一个文件时&#xff0c;发现了以上很诡异的情况&#xff08;貌似我平时也是这样编译的啊&#xff0c;&am…

Try to run this command from the system terminal. Make sure that you use the问题,亲测已解决

今天使用python的时候&#xff0c;想要导包出现了这种问题 直接进入主题&#xff0c; 个人觉得是因为python和Pip的版本不匹配&#xff0c;我是使用的python3.7然后pip使用的是22.4 导致报错&#xff0c;我就把pip退回一下成为20.2.4的&#xff0c;然后就成功了&#xff0c;注意…

Error: spawn cmd ENOENT at Process.ChildProcess._handle.onexit

解决方案&#xff1a;在系统变量 Path 里面加上&#xff1a;%SystemRoot%\system32&#xff0c;关掉终端&#xff0c;重新启动项目。 详细步骤&#xff1a; 1、点击自己的 此电脑 &#xff0c;右键 属性(R) &#xff1b; ​ 2、找到 高级系统设置 ; ​ 3、点击 高级 &#x…

Process terminated

idea点击maven下的install出现错误 解决方法&#xff1a;更换maven进行重新配置 1、环境变量中找到MAVEN_HOME&#xff0c;修改新Maven的存放路径 2、修改maven相关配置 方法一&#xff1a;如下图 方法二&#xff1a;File --》 settings &#xff08;快捷键&#xff1a;Ctr…

406错误

当请求的url后缀为.hml 并且传到页面的数据类型是json时&#xff0c;就会出现406&#xff0c; 解决&#xff1a; 在web.xml中添加一个mapping&#xff0c;然后访问url后缀改为.action

KBL406-ASEMI插件整流桥KBL406

编辑&#xff1a;ll KBL406-ASEMI插件整流桥KBL406 型号&#xff1a;KBL406 品牌&#xff1a;ASEMI 封装&#xff1a;KBL-4 特性&#xff1a;整流桥 正向电流&#xff1a;4A 反向耐压&#xff1a;600V 恢复时间&#xff1a;>2000ns 引脚数量&#xff1a;4 芯片个数…

记录一次生产环境偶发HTTP响应406报错问题

背景 今天在生产环境碰到了一个不算复杂&#xff0c;但是容易让人抓狂的问题。我们的一个Rest接口偶发的报406错误&#xff0c;只在生产环境中出现&#xff0c;大致估算是三千次调用中会出现十几次的这个错误&#xff0c;在测试环境一直无法复现。 首先查了下HTTP的406状态码语…

openresty线上406 Not Acceptable实战排查

因为公司需要对接平台业务&#xff0c;然后其中肯定离不开nginx来做代理转发的&#xff0c;而且我们没有http的地址&#xff0c;全是对外暴露的https的地址。今天就遇到了一些问题&#xff0c;在对接平台的时候它们调过来经过nginx总是406报错&#xff0c;今天我就带大家一起揭…

html报406错误,Ajax请求出现406的原因和解决方法

一般出现406错误有两种可能&#xff1a; 1、如果后缀是html是不能响应json数据的。需要修改后缀名。 在做伪静态化过程中&#xff0c;以.html结尾的后缀&#xff0c;做post请求时&#xff0c;不能响应json格式&#xff0c;这是spring官方做出的处理 可以加一个后缀改为.action …

SpringMVC在返回JSON数据时出现406错误解决方案

在SpringMVC框架的使用中常常会使用ResponseBody注解&#xff0c;修饰“处理器”&#xff08;Controller的方法&#xff09;&#xff0c;这样在处理器在返回完毕后&#xff0c;就不走逻辑视图&#xff0c;而是将返回的对象转成JSON字符串响应给客户端&#xff0c;但这种操作有时…

Spring MVC 406

使用Spring MVC返回 JSON 数据有时候会在页面报出以下 406 错误。具体错误信息如下&#xff1a; 最常见的问题就是缺少 Jackson 工具包&#xff0c;它的作用是把 Java 对象转换成 JSON 输入出页面。当然这是最常见的情况&#xff0c;下面我就来介绍一下项目中出现的问题。由于项…